An article I just read on Eric Lippert’s blog finally made me realize the full justification for why I write C/C++ multi-line macros the way I do. Now to build up the justification piece by piece…
Imagine you are writing a macro, DoStuff(), which must call two functions in sequence, Func1() and Func2(). Naïvely, one might write:
#define DoStuff() Func1(); Func2();
Now say you want to execute DoStuff() only if SomeTest() returns true. One might reasonably write:
if (SomeTest())
DoStuff();
What does the C preprocessor expand this into?
if (SomeTest())
Func1(); Func2();;
Due to C’s syntax rules, the semicolon after Func1() will terminate the if statement. This means that Func2() will be executed regardless of the returned value of SomeTest()! Not exactly the behavior the user of the macro expected!
There is also an extra semicolon after Func2(), but it is harmless in this instance as it will be interpreted by the compiler as a null statement. However, this extra semicolon will end up being rather important in the next example!
The next idea might be to encase the macro in a scoping block using { and }. The macro would then look like:
#define DoStuff() { Func1(); Func2(); }
Expanding the macro as in the above example will result in the code:
if (SomeTest())
{ Func1(); Func2(); };
Excellent — the code now functions as expected, with the extra semicolon harmlessly terminating the if statement. However, there’s a different problem with this construct. Consider the following use of the macro:
if (SomeTest())
DoStuff();
else
Func3();
This will be expanded by the C preprocessor into:
if (SomeTest())
{ Func1(); Func2(); };
else
Func3();
As I mentioned before, the semicolon at the end of the { } block will terminate the if statement, which means that the else statement is no longer bound to any if, and the code will not compile! Uh-oh!
Naturally, there is a solution, which is to use a do { ... } while (0) construct when writing multi-line macros, as follows:
#define DoStuff() do { Func1(); Func2(); } while (0)
The C preprocessor will expand this into:
if (SomeTest())
do { Func1(); Func2(); } while (0);
else
Func3();
This code compiles and acts exactly how a user of the macro would expect it to. Therefore, when writing multi-line macros, be sure to encase them in do { ... } while(0).
Recent Comments