Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you create a debug only function that takes a variable argument list? Like printf()

People also ask

What is argument list in printf?

printf is a "variadic" function. This means that the argument list is declared with ... on the end, and in the implementation of printf the va_list , va_start , va_arg etc macros are used to extract the arguments from the variable length list.

How many arguments does the printf function take?

C Program 2.1 contains a printf() statement with only one argument, that is, a text string. This string is referred to as the message string and is always the first argument of printf().


I still do it the old way, by defining a macro (XTRACE, below) which correlates to either a no-op or a function call with a variable argument list. Internally, call vsnprintf so you can keep the printf syntax:

#include <stdio.h>

void XTrace0(LPCTSTR lpszText)
{
   ::OutputDebugString(lpszText);
}

void XTrace(LPCTSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);
    int nBuf;
    TCHAR szBuffer[512]; // get rid of this hard-coded buffer
    nBuf = _vsnprintf(szBuffer, 511, lpszFormat, args);
    ::OutputDebugString(szBuffer);
    va_end(args);
}

Then a typical #ifdef switch:

#ifdef _DEBUG
#define XTRACE XTrace
#else
#define XTRACE
#endif

Well that can be cleaned up quite a bit but it's the basic idea.


This is how I do debug print outs in C++. Define 'dout' (debug out) like this:

#ifdef DEBUG
#define dout cout
#else
#define dout 0 && cout
#endif

In the code I use 'dout' just like 'cout'.

dout << "in foobar with x= " << x << " and y= " << y << '\n';

If the preprocessor replaces 'dout' with '0 && cout' note that << has higher precedence than && and short-circuit evaluation of && makes the whole line evaluate to 0. Since the 0 is not used the compiler generates no code at all for that line.


Here's something that I do in C/C++. First off, you write a function that uses the varargs stuff (see the link in Stu's posting). Then do something like this:


 int debug_printf( const char *fmt, ... );
 #if defined( DEBUG )
  #define DEBUG_PRINTF(x) debug_printf x
 #else
   #define DEBUG_PRINTF(x)
 #endif

 DEBUG_PRINTF(( "Format string that takes %s %s\n", "any number", "of args" ));

All you have to remember is to use double-parens when calling the debug function, and the whole line will get removed in non-DEBUG code.


Another fun way to stub out variadic functions is:

#define function sizeof

Ah, vsprintf() was the thing I was missing. I can use this to pass the variable argument list directly to printf():

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

void DBG_PrintImpl(char * format, ...)
{
    char buffer[256];
    va_list args;
    va_start(args, format);
    vsprintf(buffer, format, args);
    printf("%s", buffer);
    va_end(args);
}

Then wrap the whole thing in a macro.


@CodingTheWheel:

There is one slight problem with your approach. Consider a call such as

XTRACE("x=%d", x);

This works fine in the debug build, but in the release build it will expand to:

("x=%d", x);

Which is perfectly legitimate C and will compile and usually run without side-effects but generates unnecessary code. The approach I usually use to eliminate that problem is:

  1. Make the XTrace function return an int (just return 0, the return value doesn't matter)

  2. Change the #define in the #else clause to:

    0 && XTrace
    

Now the release version will expand to:

0 && XTrace("x=%d", x);

and any decent optimizer will throw away the whole thing since short-circuit evaluation would have prevented anything after the && from ever being executed.

Of course, just as I wrote that last sentence, I realized that perhaps the original form might be optimized away too and in the case of side effects, such as function calls passed as parameters to XTrace, it might be a better solution since it will make sure that debug and release versions will behave the same.