1.防止错误展开
请注意宏定义中出现的所有这些括号,它们的作用是预防引起与优先级有关的问题。
例如,假设宏abs被定义成了这个样子:
让我们来看abs(a-b)求值后会得到怎样的结果。表达式
abs(a-b)
会被展开为
a-b>0?a-b:-a-b
这里的子表达式-a-b不是我们期望的-(a-b),因此上式无疑会得到一个错误的结果。因此,我们最好在宏一定中把每个参数都用括号括起来。同样,整个结果表达式也应该用括号括起来,以防止当宏用于一个更大一些的表达式中可能出现的问题。如果不这样,
abs(a)+1
展开后的结果为:
a>0?a:-a+1
这个表达式很显然是错误的。
2.防止二次重算
一个操作数如果在两处被用到,就会被求值两次。例如,在表达式max(,ab)中,如果a大于b,那么a将被求值两次:第一次是在a与b比较期间,第二次是在计算max应该得到的结果值时。
这种做法不但效率低下,而且可能是错误的:
如果max是一个真正的函数,上面的代码可以正常工作;而如果max是一个宏,那么就不能正常工作。要看清楚这一点,我们首先初始化数组x中的一些元素:
x[0] = 2;
x[1] = 3;
x[2] = 1;
然后考擦在循环的第一次迭代时会发生上面。上面代码中的赋值语句将被扩展为:
首先,变量biggest将与x[i++]比较。因为i此时的值是1,x[1]的值是3,而变量biggest此时的值是x[0]即2,所以关系运算的结果为false。这里,因为i++的副作用,在比较后i递增为2。
因为关系运算的结果为false,所以x[i++]的值将被赋给变量biggest。然而,经过i++的递增运算后,i此时的值是2。所以,实际上赋给变量biggest的值是x[2],即1。这时,又因为i++的副作用,i的值成为3。
解决这类问题的一个办法是,确保宏max中的参数没有副作用。
另一个办法是让max作为函数而不是宏,或者直接编写比较两数取较大者的运算的代码。
3.宏的效益
使用宏的另一个危险是,宏展开可能产生非常庞大的表达式,占用的空间远远超过了编程者所期望的空间。例如,让我们再看宏max的定义:
假定我们需要使用上面定义的宏max,来找到a、b、c、d四个数的最大者,最显而易见的写法是:
上面的式子展开后就是:
确实,这个式子太长了!
其实,写成以下代码似乎更容易一些:
4.宏不是类型定义
考虑下面的代码:
从上面两个定义来看,T1和T2从概念上完全符同,都是指向结构foo的指针。但是,当我们试图用它们来声明多个变量时,问题就来了:
第一个声明被扩展为:
这个语句中a被定义为一个指向结构的指针,而b却被定义为一个结构(而不是指针。第二个声明则不同,它定义了a和b都是指向结构的指针,因为这里T2的行为完全与一个真实的类型相同。