Using Increment and Decrement Operators in Macro Arguments

If a function-like macro evaluates an argument more than once, you should avoid passing it an expression that contains an increment or decrement operator:

#include <stdio.h>

#define ABS(value) ( (value) >= 0 ? (value) : -(value) )

main()

{

int array[4] = {3, -20, -555, 6};

int *ptr = array;

int val, count;

for( count = 0; count < 4; count++ )

{

val = ABS(*ptr++); /* Error! */

printf( "abs of array[%d] = %d\n", count, val );

}

}

The program uses the ABS macro that was used to explain macros in Chapter 7, “Preprocessor Directives.” The macro returns the absolute value of the argument you pass to it.

The goal in this program is to display the absolute value of every element in array. It uses a for loop to step through the array and a pointer named ptr to access each array element in turn. Instead of the output you would expect,

abs of array[0] = 3

abs of array[1] = 20

abs of array[2] = 555

abs of array[3] = 6

the program gives this output:

abs of array[0] = -20

abs of array[1] = -6

abs of array[2] = 8307

abs of array[3] = 24864

(The last two array values may differ if you run the program. They are the contents of memory not used by the program.)

The error occurs in this line,

val = ABS(*ptr++); /* Error! */

which QuickC expands as shown here:

val = ( (*ptr++) >= 0 ? (*ptr++) : -(*ptr++) ); /* Error! */

Because it uses the conditional operator, the ABS macro always evaluates its argument at least twice. This isn't a problem when the argument is a constant or simple variable. In the example, however, the argument is the expression *ptr++. Each time the macro evaluates this expression, the increment operator takes effect, causing ptr to point to the next element of array.

The first time the program invokes the macro, ptr points to the first array element, array[0]. Since this element contains a nonnegative value (3) the macro evaluates the argument twice. The first evaluation takes the value that ptr points to and then increments ptr. Now ptr points to the second element, array[1]. The second evaluation takes the value of array[1] and increments ptr again.

The first macro invocation not only returns an incorrect value (–20, the value of array[1] ), it also leaves ptr pointing to the third array element, making the results of later invocations unpredictable. (The pointer eventually moves past the last element of array and points to unknown data.)

To avoid the problem, don't use the increment or decrement operators in arguments you pass to a macro. This revision removes the error by incrementing ptr in the for statement instead of the macro invocation:

#include <stdio.h>

#define ABS(value) ( (value) >= 0 ? (value) : -(value) )

main()

{

int array[4] = {3, -20, -555, 6};

int *ptr = array;

int val, count;

for( count = 0; count < 4; count++, ptr++ )

{

val = ABS(*ptr);

printf( "abs of array[%d] = %d\n", count, val );

}

}

This advice applies generally to QuickC library routines as well as macros you write. Remember, some run-time library routines are implemented as macros rather than C functions. If you're not sure whether a library routine is actually a macro, look it up in online help.