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!

4 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. 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.

    Why not? C allows you to put arbitrary {} blocks of codes any place a single statement would be allowed. This code:

    #include

    #define swap( a, b ) { int temp = a; a = b; b = temp; }

    void main( int argc, char *argv[] ) {
    int x = 17;
    int y = 28;
    swap(x, y);
    printf("x = %d, y = %d\n", x, y);
    }

    ReplyDelete
  3. (Huh. For whatever reason, maybe it's the angle brackets, Blogger gets rid of open-angle stdio.h close-angle in the snippet above. Which is why I deleted my first version of this comment.)

    ReplyDelete
  4. @Tom: You say "But this won't work outside an if statement," but I think it's the other way around. It will /only/ work outside an if-statement.

    The problem is that "{ ... stuff ... };" works fine as a stand-alone block, but it's actually two statements in one. Crucially: the curly braces form a compound statement, and the semi-colon gives an empty statement. If you have it as the body of the "then" part of an if-then-else, the fact that it is two statements breaks things.

    Consider this example program:

    #define swap(x,y) { int a = x; x = y; y = a; }

    volatile int c;

    int main()
    {
    int x = 2, y = 3, z = 4;

    if (c)
    swap(x, y);
    else
    swap(y, z);

    return 0;
    }

    The compiler barfs on the first 'swap' with this error:

    swap.c:12: error: ‘else’ without a previous ‘if’

    And that's because of the semicolon after the macro invocation. If you really want your macro call to look like a function call, you have to give it the do { ... } while (0) wrapper.

    ReplyDelete