Menu

Use Variadic Macro for Debug Printing

2024-02-05 - General Programming

In Debug-Printing, it is required to provide information useful enough so that problems can be diagnosed easily. If the printed messages include source location, it will be extremely useful for newly participating contributors. This blog will introduce the method of implementing such a debug printer.

Make Your Own Debug Printer

First of all, let’s make a debug printer function that prints out the source location:

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

int vprintf2(const char* src_fn,const int src_ln,const char* format,va_list arg_list)
{
    char buff[512];    // Adjust the buffer size to match your favor!
    int a=snprintf(buff,sizeof(buff),"[%s@%d] ",src_fn,src_ln);
    int b=vsnprintf(&buff[a],sizeof(buff)-a,format,arg_list);
    fwrite(buff,sizeof(char),a+b,stdout);
    return a+b;
}

int printf2(const char* src_fn,const int src_ln,const char* format,...)
{
    int ret;
    va_list arg_list;
    va_start(arg_list,format);
    ret=vprintf2(src_fn,src_ln,format,arg_list);
    va_end(arg_list);
    return ret;
}

Feel free to replace fwrite with other functions to suit your environment (e.g.: OutputDebugStringA in Windows).

Wrap With Macro

You will feed __FILE__ and __LINE__ as the first and second parameters to printf2. However, this can be impractical and inefficient. We will need to use macros. The __VA_ARGS__ can be used in macro to pass through “…” arguments. However, please note such definition is incorrect:

#define printf3(fmt,...)    printf2(__FILE__,__LINE__,fmt,__VA_ARGS__)

If the variadic part is unused, the C preprocessor will still leave a comma before the ending parenthesis. For example, printf3("Hello"); will be interpreted into printf2(__FILE__,__LINE__,"Hello",); so that the compiler will throw errors about this. The MSVC doesn’t seem to have this issue. However, to adapt your code for clang and GCC, you will need to handle this trailing comma issue.
Historically, GNU has an extension to handle the trailing comma. The ## token paste operator has a special meaning when placed between this comma and variadic argument. So, let’s adjust our printf3 definition:

#define printf3(fmt,...)    printf2(__FILE__,__LINE__,fmt,##__VA_ARGS__)

Albeit this is GNU’s extension, it also works for MSVC. You don’t have to make conditional compilation statements for it.

Security Risk

In the Release version, your program must not leak sensitive information such as source file name and line numbers. Therefore, you should use conditional preprocessor to wrap it up:

#if defined(_DEBUG)
#define printf3(fmt,...)    printf2(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#else
#define printf3(fmt,...)    printf(fmt,##__VA_ARGS__)
#endif

Summary

And that’s it! Now you will be able to put source location into the debug-printing statements just like you are using the normal printf but the source location is also included. This is a very useful approach in large-scale software to help new contributors debug and get familiar with the codes.

Reference

Variadic Macros (The C Preprocessor): https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

Leave a Reply

Your email address will not be published. Required fields are marked *