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