Saturday, May 12, 2012

strange programmer habits : do...while(0) in macros

I wrote previously about the strange habit some developers have of using do...while(0) to avoid using gotos. After a recent conversation with my co-worker Cory, I learned do...while(0) blocks are even more useful.

Consider the situation where you want to have a C macro that swaps two values. Here's a naive implementation that swaps two integers:
#define swap( a, b ) int temp = a; a = b; b = temp;
And you might use it like this:
int x = 12;
int y = 17;
swap( x, y );
printf( "(x,y) = (%d, %d)\n", x, y );
And since you executed the swap after assigning values to x & y and before printing, you would expect this fragment to print out something like this:
(x,y) = (17,12)
But there are problems with this macro. Let's say we want to use it in an if...else clause, like this:
int accumulator = 2317;
int count = 0;
/* do some stuff */
if( accumulator > 1000 )
  swap( x, y );
else
  count++; 
If you tried something like this, you would probably get an error telling you the compiler found an else unrelated to a previous if. And here's why...

C macros operate like direct textual replacements, so when the compiler sees the string "swap( x, y )" it replaces it with whatever value it had for the macro, producing an if...else clause like this:
if( accumulator > 1000 )
  int temp = x;
  x = y;
  y = temp;;
else
  count++;
The syntax of the C language says that you get either a curly-brace enclosed block or a single statement following an if (). The problem here is that macro substitution has inserted multiple statements (that are not part of a curly-brace enclosed block) after the if. You could define the swap macro like this:
#define swap( a, b ) { int temp = a; a = b; b = temp; }
But this won't work outside an if statement.
And this is where the do { ... } while( 0 ) comes in handy. If we define our swap macro like this, we'll get the correct behavior in if's, for's and as just plain statements:
#define swap( a, b ) do { int temp = a; a = b; b = temp; } while ( 0 )
Try it yourself; here's a quick program that defines two macros: swap and swap_naive. The former "does the right thing" while the latter doesn't. The program accepts a command line option, converts it to an integer and swaps different variable depending on whether the option is greater than 50. This program won't compile until you replace the "swap_naive" macro calls with plain ol' "swap" calls:

#include <stdio.h>
#include 
<stdlib.h>
#define swap_naive( a, b ) temp = a; a = b; b = temp;
#define swap( a, b ) do { int t = a; a = b; b = t; } while ( 0 )
void main( int argc, char *argv[] ) {
  int x = 17;
  int y = 19;
  int z = 23;
  int temp;
  int c;
  if( argc > 1 ) {
    c = atoi( argv[1] );
    printf( "comparison value lifted from command line: %d\n", c );
  } else {
    c = 12;
    printf( "using default comparison value: 12\n" );
  }
  printf( "x, y and z before swap: x = %d, y = %d, z = %d\n", x, y, z );
  if( c > 50 )
    swap_naive( x, y );
  else
    swap_naive( y, z );
  printf( "after swap: x = %d, y = %d, z = %d\n", x, y, z );
}

Cheers!