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.
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:
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:
Cheers!
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;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:
int y = 17;
swap( x, y );
printf( "(x,y) = (%d, %d)\n", x, y );
(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;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...
int count = 0;
/* do some stuff */
if( accumulator > 1000 )
swap( x, y );
else
count++;
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 )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:
int temp = x;
x = y;
y = temp;;
else
count++;
#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><stdlib.h>
#include
#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!
This comment has been removed by the author.
ReplyDeleteYou could define the swap macro like this:
ReplyDelete#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);
}
(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@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.
ReplyDeleteThe 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.