2

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 ;)

5
  • 1
    Just place the function definition between preprocessor statements #if DEBUG_ENABLED == 1 and #endif. Commented Jul 27, 2020 at 13:46
  • I don't think this has anything to do with the preprocessor. Commented Jul 27, 2020 at 13:47
  • 1
    Why not create a macro calling a printing function based on defined symbol? Commented Jul 27, 2020 at 13:51
  • 1
    See also #define macro for debug printing in C?. Commented Jul 27, 2020 at 14:12
  • @EugeneSh. This is an alternative but it is forcing me to call something like #ifdef DEBUG debug_log(...) #endif everytime I call want to print the log. In fine I want only debug_log(...) Commented Jul 27, 2020 at 14:42

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

16 Comments

What is the compiler behaviour to a (void)0 instruction ? Is there an equivalent instruction in assembly or is it totally ignored ?
Most likely, this would be in a header file where the external function definition would be inappropriate. It could be made static inline, or the macro could call an external function with variable arguments. It actually needs to be a macro because the __func__ and __LINE__ would not expand to the correct thing if it was a function.
@Amaury This creates an expression with the same return type as the function. That way it can be dropped in place anywhere the real function would otherwise be called. As it is a statement expression with no side effects, compilers will typically optimize it away.
@JohnBollinger The cast to (void) usually suppresses warnings about expressions that have no effect.
@Amaury See my edit. I made some changes so you should get the correct file/line#.
|
2

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.

2 Comments

Important point !! With your method it is better to add ##__VA_ARGS__ instead of VA_ARGS. Otherwise when no additionnal argument is passed, the compiler will append a coma for example snprintf will look like : snprintf(str, sizeof(str), msg, ); This will not compile.
@Amaury I was trying to avoid GCC extensions as much as possible and the ## __VA_ARGS__ is a GCC extension. That's why I added a second version of the macro where the initial part of the message is to be included within the .../__VA_ARGS__.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.