C语言中宏是一个很好的工具,但也容易引起错误。
1. 宏不是函数
#define max(a,b) ((a)>(b)?(a):(b))
请注意宏定义中的括号,它们的作用就是预防引起与优先级有关的问题。没有这些括号,当宏展开的时候就可能引起意外的结果
biggest = x[0];
i = 1;
while(i < n)
biggest = max(biggest, x[i++]);
如果max是一个真正的函数,上面的代码可以正常工作,但是max如果是一个宏,那么就不能正常工作。展开一下:
biggest = ((biggest)>(x[i++])?(biggest):(x[i++]));
首先变量biggest与x[i++]比较。假设此时i值为1,同时biggest小于x[1],那么关系运算结果为false。注意,因为i++有副作用,在比较后i递增为2,所以三目运算结果是x[2]。这明显不符合我们的预期,同时冒号后面的表达式还要经历一次副作用,整条语句结束后i的值是3。
而解决此类问题的一个办法是,确保max中的参数没有副作用。
2. 宏不是语句
__FILE__
和__LINE__
是内建于C语言预处理器中的宏。
assert宏
#define assert(e) if (!(e)) assert_error(__FILE__,__LINE__)
assert宏这个定义,即使用在一个再明白不过的情形中,也会有一些难以察觉的错误:
if (x > 0 && y > 0)
assert(x > y);
else
assert(y > x);
但是将其展开并左适当的缩排处理后,看一下:
if (x > 0 && y > 0)
if(! (x > y))
assert_error("foo.c",37);
else
if(! (y > x))
assert_error("foo.c",39);
因为C语言规定,else与它上面最相邻的if语句对齐。
所以这样的情况下,还是老实一点填写上括号比较好。
3.宏并不是类型定义
#define T1 struct foo *
typedef struct foo * T2;
从上面两个定义来看,T1和T2从概念上完全相同,都是指向结构foo的指针。但是,当我们试图用它们来声明多个变量时,问题来了:
T1 a, b;
T2 a, b;
第一个声明被扩展为:
struct foo *a, b;
第二个声明则不同,它定义了a和b都是指向结构的指针,因为这里T2的行为完全与一个真实的类型相同。