[C 陷阱与缺陷] (六) 预处理器

码字不易,对你有帮助 点赞/转发/关注 支持一下作者

微信搜公众号:不会编程的程序圆

看更多干货,获取第一时间更新

代码,练习上传至:

https://github.com/hairrrrr/C-CrashCourse

看更多干货
https://mp.weixin.qq.com/s/QkO9wYp8M3PqlXwqaMhFBw

一 预处理器

在严格意义上的编译过程开始之前,C 语言预处理器首先对程序代码作了必要的转换处理。因此,我们运行的程序实际上并不是我们所写的程序。预处理器使得编程者可以简化某些工作,它的重要性可以由两个主要的原因说明(当然还有一些次要原因,此处就不赘述了)。

第一个原因是,我们也许会遇到这样的情况,需要将某个特定数量(例如,某个数据表的大小)在程序中出现的所有实例统统加以修改。我们希望能够通过在程序中,只改动一处数值,然后重新编译就可以实现。预处理器要做到这一点可以说是轻而易举,即使这个数值在程序中的很多地方出现。我们只需要将这个数值定义为一个显式常量(manifest constant), 然后在程序中需要的地方使用这个常量即可。而且,预处理器还能够很容易地把所有常量定义都集中在一起, 这样要找到这些常量也非常容易。

第二个原因是,大多数 C 语言实现在函数调用时都会带来重大的系统开销。因此,我们也许希望有这样一种程序块, 它看上去像一个函数, 但却没有函数调用的开销。举例来说,getchar 和 putchar 经常被实现为宏,以避免在每次执行输入或者输出一个字符这样简单的操作时,都要调用相应的函数而造成系统效率的下降。

虽然宏非常有用,但如果程序员没有认识到宏只是对程序的文本起作用,那么他们很容易对宏的作用感到迷惑。也就是说,宏提供了一种对组成 C 程序的字符进行变换的方式,而并不作用于程序中的对象。因而,宏既可以使一段看上去完全不合语法的代码成为一个有效的 C 程序,也能使一段看 上去无害的代码成为一个可怕的怪物。

1. 不能忽视宏定义中的空格

一个函数如果不带参数,在调用时只需在函数名后加上–对括号即可加以调用了。而一个宏如果不带参数,则只需要使用宏名即可,括号无关紧要。只要宏已经定义过了,就不会带来什么问题:预处理器从宏定义中就可以知道宏调用时是否需要参数。与宏调用相比,宏定义显得有些“暗藏机关”。例如,下面的宏定义中f是否带了一个参数呢?

#define f (x) ((x)-1)

答案只可能有两种:

或者 f(x) 代表 ((x)-1)

或者 f 代表 (x) ((x)-1)

在上述宏定义中,第二个答案是正确的,因为在 f 和后面的 (x) 之间多了一个空格!所以,如果希望定义 f(x) 为 ((x)-1),必须像下面这样写:

#define f(x) ((x)-1)

这一规则不适用于宏调用,而只对宏定义适用。因此,在上面完成宏定义后,f(3) 与 f (3) 求值后 都等于 2。

2. 宏并不是函数

因为宏从表面上看其行为与函数非常相似,程序员有时会禁不住把两者视为完全等同。因此,我们常常可以看到类似下面的写法:

#define abs(x) (((x)>=0)?(x) :-(x))

或者:

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

请注意宏定义中出现的所有这些括号,它们的作用是预防引起与优先级有关的问题。例如,假设宏 abs 被定义成了这个样子:

#define abs(x) x>0?x:-x

让我们来看 abs(a-b) 求值后会得到怎样的结果。表达式

abs(a-b)

会被展开为

a-b>0?a-b:-a-b

这里的子表达式 -a-b 相当于 (-a)-b,而不是我们期望的 -(a-b),因此上式无疑会得到一个错误的结果。因此,我们最好在宏定义中把每个参数都用括号括起来。同样,整个结果表达式也应该用括号括起来,以防止当宏用于一个更大一些的表
达式中可能出现的问题。如果不这样,

abs(a)+1

展开后的结果为:

a>0?a:-a+1

这个表达式很显然是错误的,我们期望得到的是 -a,而不是 -a+1! abs 的正确定义应该是这样的:

#define abs(x) (((x)>=0)?(x):-(x))

这时,abs (a-b) 才会被正确地展开为: ((a-b)>0? (a-b):-(a-b))
而 abs (a)+1 也会被正确地展开为: ((a)>0?(a):-(a))+1

即使宏定义中的各个参数与整个结果表达式都被括号括起来,也仍然还可能有其他问题存在,比如说,一个操作数如果在两处被用到,就会被求值两次。例如,在表达式 max(a,b )中,如果 a 大于 b,那么 a 将被求值两次:第一次是在 a 与 b 比较期间,第二次是在计算 max 应该得到的结果值时。

这种做法不但效率低下,而且可能是错误的:.

biggest = x[0];
i = 1;
while (i < n)
	biggest = max(biggest, x[i++]);

如果max是一个真正的函数,上面的代码可以正常工作;而如果max是一个宏,那么就不能正常工作。要看清楚这一点,我们首先初始化数组x中的一些元素:

x[0] = 2;
x[1] = 3;
x[2] = 1;

然后考察在循环的第一次迭代时会发生什么。上面代码中的赋值语句将被扩展为:

biggest = 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值