宏并不是函数
#define abs(x) (((x) >= 0) ? (x) : -(x))
#define max(a, b) ((a) > (b) ? (a) : (b))
宏定义中的出现的所有这些括号,它们的作用是预防引起与优先级有关的问题。
#define abs(x) x > 0 ? x : -x
abs(a - b)展开的结果为:
a - b > 0 ? a - b : -a - b
这里的子表示-a - b相当于(-a)-b,而不是我们期望的-(a-b),因此会得到一个错误结果。因此,我们最好在宏定义中把每个参数都用括号括起来。同时,这个结果表达式也应该用括号括起来,以防止当宏作用于一个更大一些的表达式时可能引起的问题。
abs(a) + 1展开的结果为:
a > 0 ? a : -a + 1
这个表达式显示是错误的。abs正确的定义应该是这样的:
#define abs(x) (((x) >= 0) ? (x) : -(x))
abs(a - b)正确展开为:
(((a - b) >= 0) ? (a - b) : -(a - b))
abs(a) + 1正确展开为:
(((a) >= 0) : (a) : -(a)) + 1
即使宏定义中的各个参数与整个结果表达式都被括号括起来,也仍然可能存在其他问题,比如,一个操作数如果在两处被用到,就会被求值两次。例如,在表达式中max(a, b)中,如果a大于b,那么a将被求值两次---第一次是在与a与b比较期间,第二次是在计算max应该得到的结果值时:这种做法不但效率低下,而且可能是错误的:
biggest = x[0];
i = 1;
while (i < n) {
biggest = max(biggeset, x[i++]);
}
max是函数,代码能正常工作;如果max是一个宏,那么就不能正常工作。
x[0] = 2;
x[1] = 3;
x[2] = 1;
bigggist = ((biggest) > (x[i++]) ? (biggest) : (x[i++]));
首先,变量biggest将与x[i++]比较。计算后i的值会加1,就不是原来的值了。
解决这类问题的一个办法是,确保宏max中的参数没有副作用:
biggest = x[0];
for (i = 1; i < n; i++) {
biggest = max(biggest, x[i]);
}
另一个办法是让max作为函数而不是宏,或者直接编写用来比较两数且取较大者的运算代码:
biggest = x[0];
for (i = 1; i < n; i++) {
if (x[i] > biggest) {
biggest = x[i];
}
}
下面这个宏混合了宏和递增运算的副作用,代码显得岌岌可危。
#define putc(x, p) (--(p)->_cnt >= 0 ? (*(p)->_ptr++ = (x)) : _flsbuf(x, p))
x是将要写入文件的字符,p是一个指针,指向一个用于描述文件的内部数据结构。请注意这里的x
,它极有可能是类似于*z++这样的表达式。景观x在宏putc的定义中两个不同的地方出现了两次,
但是因为这两次出现的地方是在运算符:的两侧,所以x只会被求值一次。
第二个参数p则恰恰相反,它代表将要写入字符的文件,总是会被求值两次。因为文件参数p一般
不需要作递增递减之类有副作用的操作,所以这很少引起麻烦。不过,ANSI C标准还是提出了警告:putc的第二个函数可能会求值两次。
将所有小写字母转换为相应的大写字母,而其他的字符则保持原状。
int toupper(int c) {
if (c >= 'a' && c <= 'z') {
c += 'A' - 'a';
}
return c;
}
在大多数C语言实现中,toupper函数在调用是造成的系统开销要远远大于函数体的实际计算操作。
#define toupper(c) \
((c) >= 'a' && (c) <= 'z' ? (c) + ('A'-'a') : (c))
toupper(*p++)
最后的结果是不正确的。
使用宏的另一个危险是,宏展开可能产生非常庞大的表达式,占用的空间会远远超过编程人员所
期望的空间。例如:
#define max(a, b) ((a) > (b) ? (a) : (b));
max(a, max(b, max(c, d)))
max(max(a, b), max(c, d))
biggest = a;
if (biggest < b) biggest = b;
if (biggest < c) biggest = c;
if (biggest < d) biggest = d;