前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >校长讲堂第十讲

校长讲堂第十讲

作者头像
一头小山猪
发布2020-04-10 10:24:17
3400
发布2020-04-10 10:24:17
举报
文章被收录于专栏:微光点亮星辰

预处理器

运行的程序并不是我们所写的程序:因为 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;

比较好一些。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微光点亮星辰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 预处理器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档