Back to Blog

A C/C++ feature that helped me to log better

Thiago Alves

I read an article some time ago where it claims that you take at least ten years to learn C/C++ entirely. I don't remember if was Bjarne Stroustrup or someone else that said that but I completely agree!

I code professionally for about eight years, and most of my career was developing in C/C++; still, I don't consider I know everything about this terrific language. One proof is that I found an interesting feature available regarding #define macros!

Let's LOG

One important thing of every software is its logs. It's used to trace program's flow or even debug on some cases where you cannot use GDB/DBX.

One important thing to put on your logs is the file name and line number where it occurs. On my early days, I thought to have found a smart way to do it!

Well, I created a log function that here I'll simplify it a little:

#include <cstdarg>
#include <cstdio>

void log(const char* fileName, int line, const char* msg, ...)  
{
    va_list args;
    va_start(args, msg);

    fprintf(stdout, "%s, %d:", fileName, line);
    vfprintf(stdout, msg, args);
    fprintf(stdout, "n");

    va_end(args);
}

Now I can use this just like a simple printf:

int var = 50;  
log("MyFile.cpp", 11, "Hello var: %d", var);  

The problem with this, show up when I add couple lines before it:

// trace var value
int var = 50;

log("MyFile.cpp", 11, "Hello var: %d", var);  

This time the output of this will still be:

MyFile.cpp, 11: Hello var: 50  

The problem is this line is wrong now! And imagine fixing all the logs of a 1000 LOC file if you just add an include?

How to solve this?

Standard Predefined Macros

If you check The GNU C Preprocessor page that talks about Standard Predefined Macros you'll find two interesting macros that solve our problem:

  • __FILE__ - This macro expands to the name of the current input file, in a C string constant type.
  • __LINE__ - This macro expands to the current input line number, in the form of a decimal integer constant.

Well, now if we use our log function with this macros we're safe:

// trace var value
int var = 50;

log(__FILE__, __LINE__, "Hello var: %d", var);  

This time the output of this will be:

MyFile.cpp, 13: Hello var: 50  

Cool right?

Yes, but if you start working with a bunch of people, you'll realize that use these standard macros might confuse some folks, and others tend to use plain printf just because they don't know how to use it.

Now, how to solve THIS problem?

#define Macros to save us

It turns out there is a feature on standard C called Variadic Macros that I didn't know before this week that solves this issue and makes log functions elegant and efficient. I'll point you again to the The GNU C Preprocessor on the Variadic Macros page so you can read the details, but in a glimpse here is its syntax:

#define MACRO_NAME(arg, ...) somefunc(arg, __VA_ARGS__)

One thing to notice though is with this syntax you must pass at least one argument as variable arguments due to the comma on your macro definition. But don't look to this post with this face! There is a second syntax of Variadic Macros that will calm you down:

#define MACRO_NAME(arg, ...) somefunc(arg, ##__VA_ARGS__)

With those double pounds before the __VA_ARGS__ keyword, the C preprocessor will know if you don't add variable arguments and will take the comma out.

How will this help us in our log function?

Easy, we create this macro:

#define LOG(msg, ...) log(__FILE__, __LINE__, msg, ##__VA_ARGS__)

Now our code snippet can be re-written to use this new macro:

// trace var value
int var = 50;

LOG("Hello var: %d", var);  

As I said: cleaner and more elegant ;-)!

Share Your Thoughts

Leave a Reply