首页 百科知识 宏不是函数

宏不是函数

时间:2023-09-22 百科知识 版权反馈
【摘要】:运行的程序并不是我们所写的程序:因为C预处理器首先对其进行了转换。例如,putchar()和getchar()通常实现为宏以避免对每一个字符的输入输出都要进行函数调用。当max()是一个真正的函数时,这会正常地工作,但当max()是一个宏的时候会失败。由于i是1而x[1]是3,这个关系是“假”。其副作用是,i增长到2。由于关系是“假”,x[i++]的值要赋给biggest。由于putc()中的文件参数可能带有副作用,这偶尔会出现问题。

6 预处理器

    运行的程序并不是我们所写的程序:因为C预处理器首先对其进行了转换。出于两个主要原因(和很多次要原因),预处理器为我们提供了一些简化的途径。

 

    首先,我们希望可以通过改变一个数字并重新编译程序来改变一个特殊量(如表的大小)的所有实例

 

    其次,我们可能希望定义一些东西,它们看起来象函数但没有函数调用所需的运行开销。例如,putchar()getchar()通常实现为宏以避免对每一个字符的输入输出都要进行函数调用。

 

6.1 宏不是函数

    由于宏可以象函数那样出现,有些程序员有时就会将它们视为等价的。因此,看下面的定义:

 

#define max(a, b) ((a) > (b) ? (a) : (b))

 

注意宏体中所有的括号。它们是为了防止出现ab是带有比>优先级低的表达式的情况。

 

    一个重要的问题是,像max()这样定义的宏每个操作数都会出现两次并且会被求值两次。因此,在这个例子中,如果ab大,则a就会被求值两次:一次是在比较的时候,而另一次是在计算max()值的时候。

 

    这不仅是低效的,还会发生错误:

 

biggest = x[0];

i = 1;

while(i < n)

    biggest =max(biggest, x[i++]);

 

max()是一个真正的函数时,这会正常地工作,但当max()是一个宏的时候会失败。譬如,假设x[0]2x[1]3x[2]1。我们来看看在第一次循环时会发生什么。赋值语句会被扩展为:

 

biggest = ((biggest) > (x[i++]) ? (biggest) :(x[i++]));

 

首先,biggestx[i++]进行比较。由于i1x[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))

 

假设我们这个定义来查找abcd中的最大值。如果我们直接写:

 

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;

 

比较好一些。

 

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈