Variadic C arguments and other GCC tricks
Q: How do you write a debug helper function passing through printf()
like data?
A: Use a combination of macros and variadic functions combined with the power of GCC extensions.
Using a macro
Very simple
#define dbg(...) \
printf(__VA_ARGS__)
Very simple as it just passes through all arguments unmodified.
Separate format argument
#define dbg(fmt, ...) \
printf(fmt, ##__VA_ARGS__)
##
is a gcc
extension also supported by clang
, but may require -Wno-gnu-zero-variadic-macro-arguments
.
It removes the trailing comma when only a single argument is passed as fmt
, which prevents compilers from aborting with a syntax error otherwise.
If you need support for other compilers too, see alternative.
Include additional information
Lets prepend the file name of the compile unit and line number of the call-site:
#define dbg(fmt, ...) \
printf("%s:%d:" fmt, __FILE__, __LINE__, ##__VA_ARGS__)
See Standard Predefined Macros for more macros.
As a function
Using a macros has the draw-back, that you blow up your code size as the expansion is done at every call-site. Therefore it might be beneficial to create a single re-usable function, which then gets called from multiple locations:
#include <stdarg.h>
static int dbg(const char *fmt, ...)
__attribute__ ((format (printf, 1, 2)));
static int dbg(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int result = vprintf(fmt, args);
va_end(args);
return result;
}
__attribute__(format)
is a gcc
extension also supported by clang
.
This allows gcc
to check, that the types of the arguments match the format string.
Combined
Using a function is less flexible compared to using a macro. But you can combine both techniques to get the best of both worlds:
- use macros to insert additional call-site specific information like file name and line number.
- use a single function for logging implementing all logic.
#include <stdarg.h>
#define dbg(fmt, ...) \
_dbg("%s:%d:" fmt, __FILE__, __LINE__, ##__VA_ARGS__)
static int _dbg(const char *fmt, ...)
__attribute__ ((format (printf, 1, 2)));
static void _va_end(va_list *args)
{
va_end(*args);
}
static int _dbg(const char *fmt, ...)
{
va_list args __attribute__ ((cleanup (_va_end)));
va_start(args, fmt);
return vprintf(fmt, args);
}
__attribute__(cleanup)
is another gcc
extension also supported by clang
.
This allows gcc
to register a cleanup function, which gets called automatically when the variable goes out-of-scope (at the end of the function).
That way va_end()
is called on all exit paths.
Also very handy to call close()
after open()
or free()
after malloc()
.