debuggingmacrospredefined-variables

Is there a Predefined Identifier for how nested a function call is?


In order to debug a recursive program, I find it useful to visualize how deeply nested my function calls are. I'd love to have something like __func__, but for how deep my stack trace is instead of what my function name is. I understand that it would be impossible for a compiler to simply know this, because how nested you are is a dynamically generated value. But it wouldn't be hard for a compiler to add capabilities to implement this, you could simply add 1 to a global counter before each call and subtract 1 before eat ret.


I'm using the following debug statement (found here):

#define printdbg(Level, formatString, ...)                          \
    do {                                                            \
        fprintf(stderr, "%s:%d %s: " formatString "\n",             \
            __FILE__, __LINE__, __func__, ##__VA_ARGS__);           \
        if (Level == LEVEL_ERROR) { printf("quitting\n"); exit(1); }\
    } while (0)

I'd love to add an additional Predefined Identifier at the start, where I can leverage something of the form printf("%*s", __NEST__+1, ":"), to print a total of __NEST__ spaces at the beginning of each debug statement, to let me visualize how deep into the stack each debug statement was made from.


I know I could simply have a global counter that I ++ at the start of each function, and -- at the end, but I just learned about predefined identifiers and they're so cool! Also, no need to re-invent the wheel.

I can't find a list of supported predefined identifiers anywhere online. All I've found is this and this, neither of which claim to be comprehensive. If the equivalent of __NEST__ exists, somebody here probably knows the one word I'm looking for. If it doesn't exist, then where can I find a well-documented list of all the predefined identifiers?


Solution

  • C does not provide a predefined identifier that will give you the function call nesting level. If a runtime system supported such an identifier it would add some overhead to all function calls. The overhead would be a price that everybody would pay and only the few using the identifier would benefit from. This is against the spirit of the C programming language, in which you expect to pay only for the features you're using.

    On most current CPU architectures and C compilers you can obtain a number that increases with each function invocation by looking at the address of a local variable. Here is an example.

    #include <stdio.h>
    
    // Base nesting level (must be initialized by main)
    static char *main_nesting;
    
    // Return an integer corresponding to the current function call nesting level
    int
    nesting_level(void)
    {
        int a;
        return (main_nesting - (char *)&a);
    }
    
    void
    nest3(void)
    {
        printf("%s=%d\n", __func__, nesting_level());
    }
    
    void
    nest2(void)
    {
        printf("%s=%d\n", __func__, nesting_level());
        nest3();
    }
    
    void
    nest1(void)
    {
        printf("%s=%d\n", __func__, nesting_level());
        nest2();
    }
    
    int
    main(int argc, char *argv[])
    {
        main_nesting = (char *)&argc;
        printf("%s=%d\n", __func__, nesting_level());
        nest1();
    }
    

    When you run this program, you will get output such as the following.

    main=20
    nest1=68
    nest2=116
    nest3=164
    

    The set of predefined macro names is specified in §6.10.8 of the ISO/IEC 9899:2018 C standard. In addition, for GCC-like compilers you can obtain a list of all predefined macros by running the following command on a Unix system.

    cpp -dM /dev/null