|
||||
|
Section 39:
|
[39.5] What should be done with macros that have multiple lines?
Answer: Choke, gag, cough. Macros are evil in 4 different ways: evil#1, evil#2, evil#3, and evil#4. Kill them all!! (Just kidding.) Seriously, sometimes you need to use them anyway, and when you do, read this to learn some safe ways to write a macro that has multiple statements. Here's a naive solution:
#define MYMACRO(a,b) \ (Bad)
statement1; \
statement2; \
... \
statementN;
This can cause problems if someone uses the macro in a context that demands a
single statement. E.g.,
while (whatever)
MYMACRO(foo, bar);
The naive solution is to wrap the statements inside {...}, such as this:
#define MYMACRO(a,b) \ (Bad)
{ \
statement1; \
statement2; \
... \
statementN; \
}
But this will cause compile-time errors with things like the following:
if (whatever)
MYMACRO(foo, bar);
else
baz;
...since the compiler will see:
if (whatever)
{
statement1;
statement2;
...
statementN;
} ; else
^^^^^^^^—compile-time error!
baz;
One solution is to use a do { <statements go here> } while
(false) pseudo-loop. This executes the body of the "loop" exactly once.
The macro might look like this:
#define MYMACRO(a, b) \ (Okay)
do { \
statement1; \
statement2; \
... \
statementN; \
} while (false) ← intentionally not including a ; here
The ; gets added by the macro's user, such as:
if (whatever)
MYMACRO(foo, bar);
else ^—the user of MYMACRO() adds the ; here
baz;
After expansion, the compiler will see this:
if (whatever)
do {
statement1;
statement2;
...
statementN;
} while (false);
else ^—this ; came from the user's code, not from MYMACRO() itself
baz;
There is an unlikely but possible downside to the above approach: historically
some C++ compilers have refused to inline-expand any function containing a
loop. If your C++ compiler has that limitation, it will not inline-expand any
function that uses MYMACRO(). Chances are this won't be a problem,
either because you don't use MYMACRO() in any inline functions, or
because your compiler (subject to all its other constraints) is willing to
inline-expand functions containing loops (provided the inline function meets
all your compiler's other requirements). However, if you are concerned, do
some tests with your compiler: examine the resulting assembly code and/or
perform a few simple timing tests.
If you have any problems with your compiler's willingness to inline-expand functions containing loops, you can change MYMACRO()'s definition to if (true) {...} else (void)0
#define MYMACRO(a, b) \
if (true) { \
statement1; \
statement2; \
... \
statementN; \
} else
(void)0 ← intentionally not including a ; here
After expansion, the compiler will see a balanced set of ifs and elses):
if (whatever)
if (true) {
statement1;
statement2;
...
statementN;
} else
(void)0;
else ^^^^^^^^—a do-nothing statement
baz;
The (void)0 in the macro definition forces users to remember the
; after any usage of the macro. If you forgot the ; like
this...
foo(); MYMACRO(a, b) bar(); ^—whoops, forgot the ; here baz();...then after expansion the compiler would see this:
foo();
if (true) {
statement1; \
statement2; \
... \
statementN; \
} else
(void)0 bar();
^—fortunately(!) this will produce a compile-time error-message
baz();
Even though the specific error message is likely to be confusing, it will at
least cause the programmer to notice that something is wrong. That's a
lot better than the alternative: without the (void)0 in the
MYMACRO() definition, the compiler would silently generate the wrong
code: the bar() call would never be called, since it would erroneously
be on the unreachable else branch of the if.
|
|||