venerdì 12 aprile 2013

C macro: do..while(0) explaination

C macros are a very powerful way to make the code readable without having to define a lot of functions in the code. One important aspect of the macros is that they can have arguments, much like an ordinary function, and therefore using a macro can be totally like a function invocation. For instance, in the following piece of code it is almost impossible to say if DO_SOMETHING is a function or a macro, except the convention that macro names are all uppercase:

if( must_work )
   DO_SOMETHING( "I'm working hard!" );
else
   printf( "I'm not working" );

Now assume that DO_SOMETHING is a macro and has the following definition:

#define DO_SOMETHING( msg ) do{ \
    if( debug_level > 0 ) \
        printf( msg ); \
    } while( 0 )

The above macro simply prints the specified message to standard output if the debug level is higher than zero.
So what are the differences between a macro that accepts parameters and an ordinary function? The first difference should be evident from the above example: the argument "msg" does not have a type, and therefore the macro must be handled with correct arguments.
A second difference is the presence of the do{ } while( 0 ) statement that surrounds the macro itself: first of all note that there is no semicolon at the end of the do..while statement so that the semicolon at the end of the whole macro is used. In other words the macro expands from:

DO_SOMETHING( "BLAH!" );

to

do{
 if( debug_level > 0 )
   printf( msg );
} while( 0 );

But why is the do..while needed at all? Does not suffice to use a couple of brackets {..} to surround the macro code? Well, they do suffice, but there is a side effect in using the brackets: the semicolon will cause the breaking of the statement that contains the macro itself.
To better explain the above, assume the macro is defined without the do..while and therefore appears as follows:

#define DO_SOMETHING( msg ) { \
  if( debug_level > 0 ) \
    printf( msg ); \
}

and place it into the code that calls it, that is then expanded it in the following:

if( must_work )
  /* DO_SOMETHING( "I'm working hard!" ); */
  {
    if( debug_level > 0 )
       printf( msg );
  };
else
  printf( "I'm not working" );

and that does not compile! The reason is that the semicolon before the else statement causes the if to stop (in other words it is a null instruction after the if closes) and the else cannot be "attached" to the if.
Therefore, having the do..while allows the caller to place a semicolon without worrying about the statement that (could) wrap the macro itself. In fact, using the do..while the above macro expands to the following working code:

if( must_work )
  /* DO_SOMETHING( "I'm working hard!" ); */
  do {
   if( debug_level > 0 )
     printf( msg );
  } while( 0 );
else
  printf( "I'm not working" );

that is legal!

Nessun commento: