When used appropriately macros are very useful, yet they are very easy to misuse. Before getting into cons and pros, first lets make sure if we really know what is a macro and how does it work.

Roughly there are three stages of compilation in C; preprocessing, compiling, linking. It really makes sense and it is very easy to understand, don’t think of this as a complex deal. We’ll mostly talk about the preprocessor stage in this post. As the name implies, this stage just pre-processes the source code before compiling it, it all happens prior to compiling and that’s it. Preprocessing includes defining some macros, and replacing each macro found in the source code with its value, so that it can be compiled. So get this right once and for all, macros are processed before compilation, they won’t be in the run-time code.

As an exercise you can use -E parameter of GCC, which will make it stop right after the preprocessing stage.

Now, let’s begin with a few examples;

#define ERROR_SEEK 152
if( errno == ERROR_SEEK )
{
    // Handle the error.
}

This directive defines a macro ERROR_SEEK. Preprocessor will replace every occurrences of ERROR_SEEK with 152 prior to compiling. So, the code that is going to be sent to compiler will have “if( errno == 152)” not the ERROR_SEEK because the compiler simply does not know what ERROR_SEEK is. Nothing fancy, dead simple. But it makes the code much more readable. Look at this example;

#ifdef __GNUC__
#define COMPILER "gcc"
#else
#define COMPILER "proprietary"
#endif

If __GNUC__ is defined somehow then every occurrences of COMPILER will be replaced with “gcc” and “proprietary” otherwise. Please note that this all happens before compilation, and this will not result in any executable code. In this special case __GNUC__ is defined by GCC itself. Though we can define a macro via #define directive in the source code, or with -D parameter of the compiler (which will work on most compilers).

Actually the name preprocessor says it all. All these preprocessor directives are pre processed before compilation. Makes sense uh ? Now I think we begin to understand the nature of macros/preprocessors.

Macros can be used to

  • Improve readability (ERROR_SEEK is much more meaningful than 152)
  • Improve maintainability (When you change ERROR_SEEK in one header, it will be replaced all over the source code)
  • Ensuring that a block of code is inlined (more on this later)

Though, if misused, the first two list items above will do  just the opposite :)

As for the possible disadvantages of preprocessors

  • Could make debugging harder as lines of source lines before and after the preprocessing will be different, so debugger can be confused while stepping the executable code and matching which source line of code it is.
  • It is easy to misuse preprocessors.
  • Could cause trouble to static code analyzers.

Now, possible misuse scenarios:

1. Operator precedence

The most common misuse scenario which you’ll see in almost every C book is this;

#define FOO_CONST 83+22
printf("%d\n", FOO_CONST * 5 ); // This macro will expand to 83 + 22 *  5, hence will result 193.

One could expect the result to be printed 525. But operator precedence would make the calculation 22 * 5 first, then add 83 to the result, so you’ll see 193 as a result instead of 525. Fixing this problem is easy;

#define FOO_CONST (83+22)
printf("%d\n", FOO_CONST * 5 ); // This macro will expand to (83+22) * 5, hence will result 525.

Enclosing the value of a macro is an easy way to ensure that they are evaluated in the right order.

2. Multiple statements

You can use multiple-statement macros to force inlining of a code block. Though note that this will increase the code size of your program. Anyway, the problem with the multiple-statements is a less known problem. Now, look at this.

#define HELLO(X) printf("Hello "); printf( X "\n" );

The problem with this code is, if you want to conditionally run this code with “if” this code will not do what you intend to do.

if(0)
    HELLO("world"); // You'd expect that this HELLO() is never "executed".

Above code will expand into

if(0)
    printf("Hello ");
printf( "world" "\n" );

As you can see in the above example only the first statement in the macro is conditionally executed, this is not what we intended for. Above code will always print “world\n”.

So, first thing comes to mind is to put them in curly brackets. Now let’s evaluate that.

#define HELLO(X) { printf("Hello "); printf( X "\n" ); }

Above code looks fine at first glance, but it will also introduce a sneaky bug that will result in compilation error. Now imagine above macro is used like this;

if(1)
    HELLO("world");
else
    HELLO("baby");

This will expand into;

if(1)
{
    printf("Hello ");
    printf("world" "\n");
}; // WE GOT ERROR HERE
else
{
    printf("Hello ");
    printf("baby" "\n");
};

So, here’s the trick which works perfectly. It is the do{} while(0) trick. OK, let’s see.

#define HELLO(X) do { printf("Hello "); printf( X "\n"); } while(0)

You can use this as you please, imagine the if else scenario.

if(1)
    HELLO("world");
else
    HELLO("baby");

This will expand into;

if(1)
    do { printf("Hello "); printf( "world" "\n" ); } while(0);
else
    do { printf("Hello "); printf( "baby" "\n" ); } while(0);

So do {} while(0) does not break when you place a semi-colon after it and also has a scope. So it’s a ideal for making sure your multiple-statements are executing as you expected.

Well, I guess that’s all I’ve got to say.