Previous | Table of Contents | Next |
A bad macro can drive a good programmer mad. Imagine the frustration when an unsuspecting programmer codes:
x = 3; y = cube( x + 1 ); z = 5 * double( x );
thinking that cube( x + 1 ) will produce the value 64 (43), only to find that y is set to 10; and thinking that 5 * double( x ) will produce the value 30, only to find that z is set to 18. The mystery becomes clear when the programmer examines the cube and double macro definitions and finds:
#define cube( x ) x*x*x #define double( x ) x+x
Given these definitions, the compiler expands cube( x + 1) as:
x + 1*x + 1*x + 1
which, because in C the * operator binds more tightly than the + operator, is equivalent to:
x + ( 1 * x ) + ( 1 * x ) + 1
When x is 3, the value of this expression is:
3 + ( 1 * 3 ) + ( 1 * 3 ) + 1
or 10.
Similarly, the compiler expands 5 * double( x ) as:
5 * x+x
which is equivalent to:
( 5 * x ) + x
When x is 3, this expression evaluates to 18.
Macros arent magicthe compiler simply replaces a macro reference with expanded text according to the macros definition. This simple text expansion requires care that the context of a macro expansion doesnt cause the resulting expression to have an unexpected meaning. You can avoid many problems with macros by following a simple rule: Put parentheses (or other explicit delimiters) around the macro text and around each macro argument within the macro text.
Following this rule, the cube and double macros can be defined as:
#define cube( x ) ( ( x ) * ( x ) * ( x ) ) #define double( x ) ( ( x ) + ( x ) )
The two assignment statements above will then expand to:
y = ( ( x + 1 ) * ( x + 1 ) * ( x + 1 ) ) z = 5 * ( ( x ) + ( x ) )
which will do what the programmer originally expected.
You may remember that some macros I presented in earlier chapters dont have so many parentheses. For example, in Chapter 4 I defined the cpystr macro as:
#define cpystr( target, source )\ strcpymax( target, source, target##_maxlen)
In this case, the expansion text is always delimited by the strcpymax function name and closing parenthesis, so theres no need for parentheses around the entire text. The target and source arguments are delimited by the commas that separate function arguments. It still wouldnt hurt to add additional parentheses as a matter of good macro programming habits, however.
Even with the protection of parentheses, a simple macro, such as double, can cause unexpected results. The second statement below is intended to increment x (to 4) and put double the new value (8) in y.
x = 3; y = double( ++x );
What it actually does is increase x to 5 and set y to 9. This results from the expanded code:
y = ( ( ++x ) + ( ++x ) );
which evaluates the macro argument twice. In this case, the argument ++x has the side effect of incrementing x, and the expanded macro does this twice instead of once, as intended. You can avoid such problems by following another rule: Never pass an expression that has side effects as a macro argument.
This example also provides additional evidence that Cs ++ and operators, which seem so simple and innocent, are often the culprits in causing unintended side effects. You may recall that in Chapter 6 I showed how ++ and can cause problems in assignment statements. The unary increment and decrement operators themselves are not really to blame; rather, its the common C programming practice of embedding an increment or decrement operation within a larger expression. C programmers frequently code
next = ary[ ++i ];
instead of
++i; next = ary[ i ];
Within simple array subscripts, using ++ or is a safe and generally comprehensible technique. You must be careful, however, to use the correct pre- or post-increment alternative. In contrast, with separate statements to increment the index and reference the array, you can always use pre-increment (e.g., ++i) because the statement order makes clear whether you are incrementing the index before or after referencing the array. In general, I recommend the use of separate statements for incrementing and decrementing array indexes because the code layout more strongly expresses the sequence of operations. This is not typical C style, but then much of whats considered standard C style stems more from habit and fashion than good programming practices.
Most problems Ive seen in C programs stem from many C programmers attitude that a simple ++i statement by itself is somehow wasteful (of what, Im not sure), and a way must be found to embed all increment and decrement operations into adjacent statements. Its a pity for those C programmers who dont follow the general guideline: Place simple increment and decrement operations in separate statements, because this guideline frees you from concerns about when ++, and side effects can cause trouble and lets you use these otherwise nice syntactic elements of C. (For systems programming, a careful embedding of ++ or may provide better performance in some cases. But in business programming, any potential advantages of such techniques are inconsequential and should not influence the way you use the ++ and operators.)
Previous | Table of Contents | Next |