Defining a variable arguments function in a preprocessor #define

Viewed 407

I am trying to define a debug_log function in a preprocessor #define in order for this function to exist only in Debug mode. The thing is that I wish to use a variable_argument function :

#ifdef DEBUG
    #define DEBUG_ENABLED 1
#else
    #define DEBUG_ENABLED 0
#endif

#define debug_log(msg, ...)                                                                 \
        do {                                                                                \
            if (DEBUG_ENABLED) {                                                            \
                char str[300];                                                              \
                int length = -1;                                                            \
                va_list argList;                                                            \
                va_start( argList, msg );                                                   \
                length = vsnprintf(str, sizeof(str), msg, argList);                         \
                va_end( argList );                                                          \
                if (length > 0)                                                             \
                {                                                                           \
                    fprintf(stderr, "%s, %d ",__func__, __LINE__);                          \
                    fprintf(stderr, "%s", str);                                             \
                    fprintf(stderr,"\n");                                                   \
                }                                                                           \
            }                                                                               \
        } while (0)                                                                         \

The compiler is returning :

error: ‘va_start’ used in function with fixed args [build] 20 | va_start( argList, msg ); \

Thank you for your help ;)

2 Answers

You're not actually defining a function here. You're defining a block of code that expects to be part of a variadic function. You're most likely not in a variadic function when you call this macro, hence the error.

Instead, define an actual function inside of an #if block, along with a dummy function-like macro in the #else block that does nothing.

#ifdef DEBUG

#define debug_log(...)  debug_log_impl(__func__, __LINE__, __VA_ARGS__)

void debug_log_impl(const char *func, int line, const char *msg, ...)
{
    char str[300];
    int length = -1;
    va_list argList;
    va_start( argList, msg );
    length = vsnprintf(str, sizeof(str), msg, argList);
    va_end( argList );
    if (length > 0)
    {
        fprintf(stderr, "%s, %d ", func, line);
        fprintf(stderr, "%s", str);
        fprintf(stderr,"\n");
    }
}

#else

#define debug_log(...) (void)0

#fi

There was a question in the comments regarding why (void)0 should be used in the #else case instead of an empty expression. Suppose you were to use debug_log as the left operand of the comma operator:

while (debug_log("iterating, x=%d",x), x>0)

With the definition above, if DEBUG is not defined this line expands to:

while ((void)0, x>0)

If it were an empty expression, it would expand to:

while (, x>0)

Which is a syntax error.

You are mixing up variable arguments for macros with variable argument access for functions.

The ... in the macro parameter list represents the variable arguments of the macro. The identifier __VA_ARGS__ in the macro replacement text expands to the variable arguments.

The ... in a function parameter list represents the variable arguments of the function. On object of type va_list can be used to access these variable arguments using the va_start, va_arg and va_end macros (and perhaps the va_copy macro if needed). Those are defined by #include <stdarg.h>.

Your debug_log macro is not a function, so it is not a function with variable arguments, so it cannot use va_start etc. to access those arguments. What it can do is pass the macros variable arguments as a whole to something else. For your example, snprintf would be a good choice as a replacement for the vsnprintf you orignally used:

#define debug_log(msg, ...)                                                                 \
        do {                                                                                \
            if (DEBUG_ENABLED) {                                                            \
                char str[300];                                                              \
                int length = -1;                                                            \
                length = snprintf(str, sizeof(str), msg, __VA_ARGS__);                      \
                if (length > 0)                                                             \
                {                                                                           \
                    fprintf(stderr, "%s, %d ",__func__, __LINE__);                          \
                    fprintf(stderr, "%s", str);                                             \
                    fprintf(stderr,"\n");                                                   \
                }                                                                           \
            }                                                                               \
        } while (0) 

The above form requires at least two arguments, so you couldn't use it to print a simple debug message such as "got here". As a workaround, the macro can be defined with no fixed parameters, omitting the msg parameter:

#define debug_log(...)                                                                      \
        do {                                                                                \
            if (DEBUG_ENABLED) {                                                            \
                char str[300];                                                              \
                int length = -1;                                                            \
                length = snprintf(str, sizeof(str), __VA_ARGS__);                           \
                if (length > 0)                                                             \
                {                                                                           \
                    fprintf(stderr, "%s, %d ",__func__, __LINE__);                          \
                    fprintf(stderr, "%s", str);                                             \
                    fprintf(stderr,"\n");                                                   \
                }                                                                           \
            }                                                                               \
        } while (0) 

That would allow you to attempt to call the macro with no parameters at all, such as debug_log();, but that will result in a compiler error at the call to snprintf.

Related