预处理器怎样处理代码

预处理器的作用是在编译器处理 C 代码前,先对源码进行一系列必要的转换。这些转换可以引入代码正常编译所需要的各个依赖项,动态修改代码以适应不同的编译环境,甚至根据需要自动生成部分 C 代码。对预处理器的合理使用,可以提高程序的可配置性。

在gcc中,通过 -E 参数生成预处理过后的文件。

 

预处理器通过以下步骤完成对代码的预处理:

1.删除源代码中的所有注释;

2.处理所有宏定义(#define),并进行展开和替换;

3.处理所有条件预编译指令(如 #if、#elif),仅保留符合条件的代码;

4.处理文件包含预编译指令(#include),将被包含文件的内容插入到该指令的所在位置;

5.处理其他可以识别的预处理指令(如 #pragma);

6.添加其他具有辅助性功能的注释信息。

 

一、宏

除去删除注释外,预处理器对代码的处理很大一部分是对于宏的处理。

1.宏函数

宏函数的运作方式与 C 函数有很大的差别,宏函数在被调用时,仅会做源代码文本上的替换,在处理宏函数参数以及宏函数体内的多行语句时,通常使用 do…while 语句等技巧,来对参数、返回值与函数体进行“封装”。

2.使用技巧

1.为宏函数的返回值添加括号

 

 宏被展开后,printf语句变为如下形式:printf("%d", 3 * 1 + 2 * 2);与想象中的完全不同,对这个宏加上括号后:(1 + x * x),结果才符合预期。

 

2.为函数的参数添加括号

 

 宏被展开后,printf语句变为如下形式:printf("%d", (1 + 1 + 2 * 1 + 2));和想象中的完全不同,对参数加上括号后:(1 + (x) * (x)),结果才符合预期。

 

3.警惕宏函数导致的多次副作用

 

 宏展开后,printf语句变为如下形式:printf("%d", (1 + (++i) * (++i)));和想象中的完全不同。

必须要注意,宏函数和 C 函数的调用是完全不同的两种方式。前者是文本上的直接替换,不会产生任何栈帧,替换后的新代码,可能计算逻辑已经发生了变化,从而导致计算错误。

 

4.定义完备的多语句宏函数

 

 这个宏被展开后没有什么变化,但是由于 if 语句没有括号,所以在任何情况下,最后的 world 都会被打印出来,原因也是因为宏只做了简单的文本替换,对于多行代码的宏函数,可以通过 do-while 语句进行处理:

do {

    printf("Hello, ");

    printf("world!");

} while(0)

 

posted @ 2022-02-07 20:35  一只吃水饺的胡桃夹子  阅读(337)  评论(0编辑  收藏  举报