前言
上一次分享了与程序有关的两种环境,分别是 翻译环境和 执行环境,在执行环境中又细分出了 预处理(预编译)、 编译、 汇编、 链接几个过程,今天就让我们来深入了解一下 预处理过程都干了些什么,话不多说,让我们开启今天的学习吧!
📖预定义符号
__FILE__----当前源文件所在的路径
__LINE__----文件当前的行号
__DATE__----文件被编译的日期
__TIME__----文件被编译的时间
__STDC__----如果编译器遵循ANSI C ,其值为1,否则未定义
__FUNCTION__----__FUNCTION__所在函数的函数名
int main() { printf("%s\n", __FILE__); printf("%d\n", __LINE__); printf("%s\n", __DATE__); printf("%s\n", __TIME__); return 0; } //上面这段代码是在VS2019这个环境下运行的,__STDC__显示未定义 //说明VS2019不支持ANSI C标准
这些预处理符号在预处理阶段就会被具体的值替换,如下图所示:
📖预处理指令
我们常见的下面这些符号都被叫做预处理指令:
- #define----定义宏和标识符常量
- #include----头文件的包含
- #pragma
- 对这些预处理指令都是在预处理阶段执行的。
📖#define
🔖#define定义标识符
语法:
#define name stuff //表示代码中所有name的地方都用stuff来进行替换
实例:
#define MAX 1000//把代码中所有的MAX用1000来替换 #define reg register//为register这个关键字,创建一个简短的名称 #define do_forever for(;;)//用更形象的符号来替换一种实现 #define CASE break;case//在写case语句的时候自动把 break写上。 //如果定义的stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续航符)
#define定义标识符的时候,要不要在最后加;?
比如:
#define MAX 1000 #define MAX 1000;
建议不要加上;,这样容易导致问题,比如下面的场景:
- 情景一:(加上;没有影响)
#define MAX 100; int main() { int min = MAX; return 0; }
上面的代码在预处理阶段,用100;去替换程序中的MAX,这就导致在text.i文件中100的后面有两个分号,其中一个时我们在源代码(text.c)中写的,另一个是在对MAX替换后得到的。这两个分号并不会对程序造成什么影响,第二个分号会被当成一条空语句去执行。
情景二:(加上;导致异常)
#define MAX 100; int main() { int i = 1; int n = 0; if(i > 0) n = MAX; else n = 0; return 0; }
可以看出,此时程序报错了。为什么呢?因为根据语法规定:if语句后面如果没有大括号的话只能有一条语句,但是从预编译的得到的text.i文件中可以看出if语句后面跟了两条语句,分别是赋值语句n = 100;和空语句;,这显然不符合语法规定,报错也是理所当然。当然针对上面的错误也有以下几种修改手段:
- 在#define的时候,后面不加;,这是一本万利的方法
- 在写源代码的时候MAX后面不写;,这种方法虽然可行,但是不符合我们的日常使用习惯,一条语句结束没有;给我们的第一感觉就是代码写的有问题
- 在写源代码的时候对if else子句加上大括号,当然这种方法也仅仅是针对当前的情况有效,如果是其他情况还是需要另寻它路。
通过分析我们得出结论:在用#define定义标识符的时候不要加;。
🔖#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro) 或定义宏(#define macro)。
语法:
#define name(parament-list) stuff
name是宏的名字
parament-list是一个用逗号隔开的符号表,它们可能会出现在stuff中(类似于参数,没有类型)
stuff会用parament-list来实现一定的功能
注意: 参数列表必须的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
实例:
#define SQUARE(x) x*x int main() { printf("%d\n", SQUARE(2)); printf("%f\n", SQUARE(5.0)); return 0; }
工作原理:
可以看出在预处理阶段对源程序中的SQUARE(2)和SQUARE(5.0)进行了替换。以SQUARE(2)为例在预处理阶段宏的参数x就是2,然后用x*x就变成了2*2再用2*2去替换SQUARE(2)最终就得到了text.i文件中的结果。
存在的陷阱一:
#define SQUARE(x) x*x int main() { printf("%d\n", SQUARE(5 + 1)); return 0; }
按照一般思维去看上面的代码,首先5+1=6我们会以为是把6传给x,然后用6*6来替换SQUARE(5 + 1)最终得到36,可结果并不像我们想的那样,正确结果是11,为什么会这样呢?去看看预处理后得到的文件我们就会恍然大悟
可以看出真实的替换结果并不是我们想的用6*6去替换SQUARE(5 + 1),而是用5+1*5+1去替换的。这说明在预处理的时候并没有执行5+1,而是直接把5+1传了过去,最终得到的结果就是11。
总结: 宏的参数是不加运算直接进行替换的
如何得到我们想要的36呢?有以下两种方法供大家参考:
对宏调用进行修改:SQUARE((5 + 1)),给5+1再加一层括号,此时在替换的时候,x就是(5+1),SQUARE((5 + 1))就会被替换成(5+1)*(5+1)最终得到的结果就是36。
对定义的宏进行修改:#define SQUARE(x) (x)*(x),此时在替换的时候,x是5+1,SQUARE(5+1)会被替换成(5+1)*(5+1)最终得到的结果也是36。
对比上面的两种方案,第一种方案带两个括号看起来比较别扭,所以更推荐第二种方案,也就是在定义宏的时候给参数带上括号。
存在的陷阱二:
//我们希望计算一个数的二倍再乘10 #define DOUBLE(x) (x)+(x)//这里定义了一个宏来计算一个数的二倍 int main() { printf("%d\n", 10 * DOUBLE(3)); return 0; }
根据需求描述,我们希望得到的应该是60,但实际却得到的是33,本质原因就是宏只会进行替换,先进行参数的替换,再进行宏体的替换。让我们来看看上面的代码经过预处理会得到什么
不难看出,为了得到我们希望的结果,应该对宏替换后得到的内容加上括号,也就是在定义宏的时候对处理结果加上括号:#define DOUBLE(x) ((x)+(x))
通过上面介绍的两种陷阱,我们可以得出一个结论:在宏定义的时候,千万不要吝啬括号!!,先给每个参数带上括号,再给stuff整体带上括号。
对于#define定义宏的注意事项总结如下:
- 参数列表必须的左括号必须与宏的名字name紧邻
- 宏的参数都是不加计算直接替换的
- 不要吝啬括号











