运行的程序并不是我们所写的程序:因为 C 预处理器首先对其进行了转换。出于两个主要原因(和很多次要原因),预处理器为我们提供了一些简化的途径。 首先,我们希望可以通过改变一个数字并重新编译程序来改变一个特殊量(如表的大小)的所有实例。 其次,我们可能希望定义一些东西,它们看起来象函数但没有函数调用所需的运行开销。例如,putchar()和 getchar()通常实现为宏以避免对每一个字符的输入输出都要进行函数调用。
6.1 宏不是函数
由于宏可以象函数那样出现,有些程序员有时就会将它们视为等价的。因此,看下面的定义:
#define max(a, b) ((a) > (b) ? (a) : (b))
注意宏体中所有的括号。它们是为了防止出现 a 和 b 是带有比>优先级低的表达式的情况。
一个重要的问题是,像 max()这样定义的宏每个操作数都会出现两次并且会被求值两次。因此,在这个例子中,如果 a 比 b 大,则 a 就会被求值两次:一次是在比较的时候,而另一次是在计算 max()值的 时候。
这不仅是低效的,还会发生错误:
biggest = x[0]; i = 1; while(i < n) biggest = max(biggest, x[i++]);
当 max()是一个真正的函数时,这会正常地工作,但当 max()是一个宏的时候会失败。譬如,假设 x[0]是 2、x[1]是 3、x[2]是 1。我们来看看在第一次循环时会发生什么。赋值语句会被扩展为:
biggest = ((biggest) > (x[i++]) ? (biggest) : (x[i++]));
首先,biggest 与 x[i++]进行比较。由于 i 是 1 而 x[1]是 3,这个关系是“假”。其副作用是,i 增长到 2。
由于关系是“假”,x[i++]的值要赋给 biggest。然而,这时的 i 变成 2 了,因此赋给 biggest 的值是 x[2]的值,即 1。 避免这些问题的方法是保证 max()宏的参数没有副作用:
biggest = x[0]; for(i = 1; i < n; i++) biggest = max(biggest, x[i]);
还有一个危险的例子是混合宏及其副作用。这是来自 UNIX 第八版的中 putc()宏的定义:
#define putc(x, p) (--(p)->_cnt >= 0 ? (*(p)->_ptr++ = (x)) : _flsbuf(x, p))
putc()的第一个参数是一个要写入到文件中的字符,第二个参数是一个指向一个表示文件的内部数据结构的指针。注意第一个参数完全可以使用如*z++之类的东西,尽管它在宏中两次出现,但只会被求值一次。
而第二个参数会被求值两次(在宏体中,x 出现了两次,但由于它的两次出现分别在一个:的两边,因此在putc()的一个实例中它们之中有且仅有一个被求值)。由于 putc()中的文件参数可能带有副作用,这偶尔会出现问题。不过,用户手册文档中提到:“由于 putc()被实现为宏,其对待 stream 可能会具有副作用。特别是 putc(c, *f++)不能正确地工作。”但是 putc(*c++, f)在这个实现中是可以工作的。
有些 C 实现很不小心。例如,没有人能正确处理 putc(*c++, f)。另一个例子,考虑很多 C 库中出现的 toupper()函数。它将一个小写字母转换为相应的大写字母,而其它字符不变。如果我们假设所有的小写字母和所有的大写字母都是相邻的(大小写之间可能有所差距),我们可以得到这样的函数:
toupper(c) { if(c >= 'a' && c <= 'z') c += 'A' - 'a'; return c; }
在很多 C 实现中,为了减少比实际计算还要多的调用开销,通常将其实现为宏:
#define toupper(c) ((c) >= 'a' && (c) <= 'z' ? (c) + ('A' - 'a') : (c))
很多时候这确实比函数要快。然而,当你试着写 toupper(*p++)时,会出现奇怪的结果。
另一个需要注意的地方是使用宏可能会产生巨大的表达式。例如,继续考虑 max()的定义:
#define max(a, b) ((a) > (b) ? (a) : (b))
假设我们这个定义来查找 a、b、c 和 d 中的最大值。如果我们直接写:
max(a, max(b, max(c, d)))
它将被扩展为:
((a) > (((b) > (((c) > (d) ? (c) : (d))) ? (b) : (((c) > (d) ? (c) : (d))))) ? (a) : (((b) > (((c) > (d) ? (c) : (d))) ? (b) : (((c) > (d) ? (c) : (d))))))
这出奇的庞大。我们可以通过平衡操作数来使它短一些:
max(max(a, b), max(c, d))
这会得到:
((((a) > (b) ? (a) : (b))) > (((c) > (d) ? (c) : (d))) ? (((a) > (b) ? (a) : (b))) : (((c) > (d) ? (c) : (d))))
这看起来还是写:
biggest = a; if(biggest < b) biggest = b; if(biggest < c) biggest = c; if(biggest < d) biggest = d;
比较好一些。