Solution

I l @ ve RuBoard

graphics/bulb.gif

Common Macro Pitfalls

1. Demonstrate how to write a simple max() preprocessor macro that takes two arguments and evaluates to the one that is greater, using normal < comparison. What are the usual pitfalls in writing such a macro?

There are four major pitfalls, in addition to several further drawbacks. Focusing on the pitfalls first, here are common ways to go wrong when writing a macro.

1. Don't forget to put parentheses around arguments.
 // Example 35-1(a): Paren pitfall #1: arguments // #define max(a,b) a < b ? b : a 

The problem here is that the uses of the parameters a and b are not fully parenthesized . Macros only do straight textual substitution, so this can cause some unexpected results. For example:

 max( i += 3, j ) 

expands to

 i += 3 < j ? j : i += 3 

which, because of operator precedence and language rules, actually means

 i += ((3 < j) ? j : i += 3) 

This could cause some long debugging sessions. There is also a problem related to sequence points and attempting to modify i twice between sequence points; more on this in a moment.

2. Don't forget to put parentheses around the whole expansion.

Fixing the first problem, we still fall prey to another subtlety.

 // Example 35-1(b): Paren pitfall #2: expansion // #define max(a,b) (a) < (b) ? (b) : (a) 

The problem now is that the entire expansion is not correctly parenthesized. For example:

 k = max( i, j ) + 42; 

expands to

 k = (i) < (j) ? (j) : (i) + 42; 

which, because of operator precedence, actually means

 k = (((i) < (j)) ? (j) : ((i) + 42)); 

If i >= j , k is assigned the value of i+42 , as intended. But if i < j , k is assigned the value of j .

We can fix problem #2 by putting parentheses around the entire macro expansion, but this leaves us with yet another problem:

3. Watch for multiple argument evaluation.

Consider what happens if one or both of the expressions has side effects:

 // Example 35-1(c): Multiple argument evaluation // #define max(a,b) ((a) < (b) ? (b) : (a)) max( ++i, j ) 

If the result of ++i >= j , i gets incremented twice, which is probably not what the programmer intended:

 ((++i) < (j) ? (j) : (++i)) 

Similarly, consider

 max( f(), pi ) 

which expands to

 ((f()) < (pi) ? (pi) : (f())) 

If the result of f() >= pi , f() gets executed twice, which is almost certainly inefficient and often wrong.

Although we could work around the first two problems, this one is a corker. There is no solution as long as max is a macro.

4. Name tromping.

Finally, macros don't care about scope. (They don't care about much of anything; see [GotW] #63. [1] ) They just perform textual substitution, no matter where the text is. This means that if we use macros at all, we have to be careful about what we name them. In particular, the biggest problem with the max macro is that it is highly likely to interfere with the standard max() function template:

[1] Available online at http://www.gotw.ca/gotw/063.htm.

 // Example 35-1(d): Name tromping // #define max(a,b) ((a) < (b) ? (b) : (a)) #include <algorithm> // oops! 

The problem is that inside header <algorithm> , there will be something like the following:

 template<typename T> const T& max(const T& a, const T& b); 

Alas, the macro "helpfully" turns that into an uncompilable mess:

 template<typename T> const T& ((const T& a) < (const T& b) ? (const T& b) : (const T& a)); 

If you think that's easy to avoid by putting your macro definition after all #included header files (which really is a good idea in any case), just imagine what the macro does to all your other code that happens to have variables or other things that just happen to be named max .

If you have to write a macro, try to give it an unusual and hard-to-spell name that will be less likely to tromp on other names .

Other Macro Drawbacks

There are a few other major things a macro can't do:

5. Macros can't recurse.

We can write a recursive function, but it's impossible to write a recursive macro. As the C++ standard [C++98] says, in 16.3.4/2:

If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file's pre-processing tokens), it is not replaced . Further, if any nested replacements encounter the name of the macro being replaced, it is not replaced . These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced .

6. Macros don't have addresses.

It's possible to form a pointer to any free or member function (for example, to use it as a predicate), but it's not possible to form a pointer to a macro, because a macro has no address. Why not should be obvious ”macros aren't code. A macro doesn't have any existence of its own, because it is only a glorified (and not particularly glorious) text substitution rule.

7. Macros are debugger-unfriendly.

In addition to the fact that macros change the underlying code before the compiler gets a chance to see it ”and therefore can wreak havoc with variable names and other names ”a macro can't be stepped into during debugging.

Have you heard the one about the scientists who started experimenting on lawyers instead of on laboratory macros? It was because

There Are Some Things Even a Macro Won't Do

There are valid reasons to use macros (for details, turn to Item 34), but there are limits. This brings us to the final question.

2. What can a preprocessor macro not create? Why not?

In the standard, clause 2.1 defines the phases of translation. Preprocessing directives and macro expansions take place in phase 4. Thus, on a compliant compiler, it is not possible for a macro to create any of the following:

  • a trigraph (trigraphs are replaced in phase 1);

  • a universal character name ( \uXXXX , replaced in phase 1);

  • an end-of-line line-splicing backslash (replaced in phase 2);

  • a comment (replaced in phase 3);

  • another macro or preprocessing directive (expanded and executed in phase 4); or

  • changes to a character literal (for example, ' x ') or string literal (for example, "hello, world" ) via macro names inside the strings.

For this last point, as noted in 16.3/8 footnote 7:

Since, by macro-replacement time, all character literals and string literals are preprocessing tokens, not sequences possibly containing identifier-like subsequences (see 2 . 1 . 1 . 2, translation phases), they are never scanned for macro names or parameters .

A published article [2] once claimed that it's possible for a macro to create a comment as follows :

[2] M. Timperley. "A C/C++ Comment Macro" ( C/C++ Users Journal , 19(1), January 2001).

 #define COMMENT SLASH(/) #define SLASH(s) /##s 

This is nonstandard and not portable, but it's an understandable mistake because it actually works on some popular compilers. Why does it work? Because those compilers don't implement the phases of translation correctly.

I l @ ve RuBoard


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net