13.宏
复杂表达式可能会被用作宏参数,这可能会因操作符优先级顺序而引发问题,除非宏定义中所有参数出现的位置都用括号括上了。对这种因参数内副作用而引发的问题,我们似乎也无能为例,除了在编写表达式时杜绝副作用(无论如何,这都是一个很好的主意)。如果可能的话,尽量在宏定义中对宏参数只进行一次求值。有很多时候我们无法写出一个可像函数一样使用的宏。
一些宏也当成函数使用(例如,getc和fgetc)。这些宏会被用于实现其他函数,这样一旦宏自身发生变化,使用该宏的函数也会受到影响。在交换宏和函数时务必要小心,因为函数参数是按值传递的,而宏参数则是通过名称替换。只有在宏定义时特别谨慎小心,才有可能减少使用宏时的担心。
宏定义中应该避免使用全局变量,因为全局变量的名字很可能被局部声明遮盖。对于那些对具名参数进行修改(不是这些参数所指向的存储区域)或被用作赋值语句左值的宏,我们应该添加相应的注释以给予提醒。那些不带参数但引用变量,或过长或作为函数别名的宏应该使用空参数列表,例如:
#define OFF_A() (a_global+OFFSET)
#define BORK() (zork())
#define SP3() if (b) { int x; av = f (&x); bv += x; }
宏节省了函数调用和返回的额外开销,但当一个宏过长时,函数调用和返回的额外开销就变得微不足道了,这种情况下我们应该使用函数。
在一些情况下,让编译器确保宏在使用时应该以分号结尾是很有必要的。
if (x==3)
SP3();
else
BORK();
如果省略SP3调用后面的分号,后面的else将会匹配到SP3宏中的那个if。有了分号,else分支就不会与任何if匹配。SP3宏可以这样安全地实现:
#define SP3() \\\\
do { if (b) { int x; av = f (&x); bv += x; }} while (0)
手工给宏定以加上do-while包围看起来很别扭,而且很多编译器和工具会抱怨在while条件是一个常量值。一个用来声明语句的宏可以使得编码更加容易:
#ifdef lint
static int ZERO;
#else
# define ZERO 0
#endif
#define STMT( stuff ) do { stuff } while (ZERO)
我们可以用下面代码来声明SP3宏:
#define SP3() \\\\
STMT( if (b) { int x; av = f (&x); bv += x; } )
使用STMT宏可以有效阻止一些可以潜在改变程序行为的打印排版错误。
除了类型转换、sizeof以及上面那些技巧和手法,只有当整个宏用括号括上时才应该包含关键字。