Introduction

Developers often find themselves in need of a reliable logging mechanism for debugging purposes. However, maintaining efficient code during production can be challenging, especially when verbose logging can impact performance. A common question arises: How do you create a debug-only function that supports a variable argument list, reminiscent of printf()?

In this blog post, we’ll explore a straightforward solution that utilizes the C/C++ preprocessor directives to create a debug logging function that can be eliminated during optimized builds while maintaining the flexibility of variable inputs.

Creating the Debug Logging Function

Step 1: Defining the Function Signature

We want our logging function to accept a format string and a variable number of arguments. The printf style function signature allows us to format strings dynamically. Below is a basic skeletal structure of the desired function:

void XTrace(LPCTSTR lpszFormat, ...);

Step 2: Using Variable Arguments

To achieve variable argument functionality, we can use the va_list, va_start, and va_end macros provided by the C standard library. This allows us to process the arguments passed to XTrace.

Here is how you can implement this:

#include <stdio.h>

void XTrace(LPCTSTR lpszFormat, ...) {
    va_list args;
    va_start(args, lpszFormat);
    int nBuf;
    TCHAR szBuffer[512]; // Consider using dynamic allocation instead
    nBuf = _vsnprintf(szBuffer, 511, lpszFormat, args);
    ::OutputDebugString(szBuffer); 
    va_end(args);
}

Key Elements of the Code:

  • va_list args: This is used to store the argument list.
  • va_start(args, lpszFormat): This initializes args to retrieve the arguments after lpszFormat.
  • _vsnprintf: This function formats the string using the argument list and writes it into a buffer.
  • OutputDebugString: Outputs the formatted string to the debugger’s output window.

Step 3: Conditional Compilation

To ensure that the debug function is removed in optimized builds, we can use preprocessor directives. By defining a macro based on a debug flag, we can control whether to include or exclude our logging function.

Example Setup:

#ifdef _DEBUG
#define XTRACE XTrace
#else
#define XTRACE
#endif
  • The macro XTRACE will point to the actual XTrace function when compiling in debug mode. In optimized builds (when _DEBUG is not defined), XTRACE will become an empty statement, effectively eliminating any debug logging code.

Putting It All Together

Here’s the complete implementation for clarity:

#include <stdio.h>
#include <stdarg.h>
#include <Windows.h> // or <Linux/string.h> depending on platform

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

void XTrace(LPCTSTR lpszFormat, ...) {
    va_list args;
    va_start(args, lpszFormat);
    int nBuf;
    TCHAR szBuffer[512];
    nBuf = _vsnprintf(szBuffer, 511, lpszFormat, args);
    ::OutputDebugString(szBuffer);
    va_end(args);
}

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

You can now use the XTRACE macro in your code as follows:

XTRACE("Warning: value %d > 3!\n", value);

Conclusion

Creating a debug-only logging function in C/C++ that can accept variable arguments is not only feasible but can be efficiently managed using preprocessor directives. This technique keeps your production code clean and performance-efficient.

Now, you can debug your applications effectively without compromising on performance in the production environment!