Friday, December 14, 2007

Top Ten #3

When we last left our hero, we were looking at ten ways to get screwed by the "C" programming language. Today's entry is 3 Unhygienic macros. The example is:

#define assign(a,b) a=(char)b
assign(x,y>>8)
which becomes
x=(char)y>>8 /* probably not what you want */

If the macro were instead written this way:

#define CHAR_ASSIGN(a,b) (a)=(char)(b)
CHAR_ASSIGN(x,y>>8)
it becomes
(x)=(char)(y>>8)

which might be what was desired. One wouldn't call it assign since it has a known side effect, which is the cast to the type char. One generally uses ALL UPPER CASE for macros, since that is the convention that nearly everyone uses in C for macros.

One might wonder why the original isn't what you want. In case it isn't obvious, here it is. If you cast an integer that is bigger than a byte to a one byte char, then shift it right 8 bits, you always get zero or -1. That's because a shift right of eight bits is the size of a char. Negative numbers shift right, but copy the sign bit (which is 1), positive numbers shift in zero bits. So at the end, you get 0 or -1.

Of course, since macros are text expansions, one tries to pass the simplest expressions to them. Some macros use their arguments more than once. So if you pass such macros an argument that has side effects, you could end up with the effects taking place more than once. For example,

#define DOUBLEIT(a) ((a)+(a))
DOUBLEIT(it++)
becomes
(it++)+(it++)

which increments the variable it twice, even though it looks like it happens just once. And, who knows what the return value is?

No comments: