13.1 简介

严格地说,预处理命令(Preprocessing Directives)并非C语言语法的组成部分,它只是对代码书写的一种约定和简化的表示方式。由于绝大多数C语言编译器都支持预处理功能,所以C标准也对预处理功能做了明确的定义和统一的规定。

编译预处理,顾名思义,是在编译之前对源代码进行的处理工作。这些处理工作在源代码中按照约定被写成预处理命令。

如图13-1所示,含有预处理命令的文本文件,即所谓源文件(Source Files),也叫做预处理文件(Preprocessing Files),需要经过预处理程序“加工”、“处理”之后才能形成编译器可以编译的不含预处理命令的翻译单元(Translation Unit)。

13.1 简介 - 图1

图13-1 预处理在编译前完成

C语言源程序可以由多个源文件组成。在一定条件下,部分源文件可以形成独立的预处理翻译单元(Preprocessing Translation Unit)单独进行预处理,单独编译。

从概念上来说,预处理与编译是两个独立的、一先一后进行地翻译源程序的过程,但在许多开发环境中,并不特别地区分它们,以至于很难察觉预处理过程的存在。

预处理器对源代码所做的“加工”无非是一些“替换”、“添加”、“拼接”、“删除”之类的工作。在代码中使用预处理命令的目的是提高编程的效率,增强代码的可读性、可维护性、可移植性,改善代码的结构,以便于调试及有条理地管理源程序代码。

13.1.1 一般特点

每条预处理命令逻辑上都占一行。预处理命令都以“#”开头。老式的C要求“#”在第一列,但现代的C语言预处理器容许“#”所在的行前面或后面有空白字符。

预处理不是一次性完成的,而是分为几个阶段逐步完成的。理解在预处理阶段预处理器工作的过程,可以对初始的源代码究竟被加工处理成什么样子有一个清楚的认识。

13.1.2 预处理的几个阶段

(1)首先进行的处理是把源文件中的不规范的字符替换为标准的C语言源字符。

比如,有的IDE可能使用特殊的字符表示文本中的换行符,这些将被替换为标准的源字符。

(2)删除换行字符。

比如下面的代码:

13.1 简介 - 图2

将被改为

13.1 简介 - 图3

再比如

13.1 简介 - 图4

将被改写为

13.1 简介 - 图5

进行这样的处理,是因为预处理命令都是以行为单位的,只有先处理了这个问题,预处理器才能更容易地识别出各条预处理命令。

(3)识别预处理单词(1)(Preprocessing Tokens)并把注释部分替换为空格。

(4)开始执行预处理命令,进行宏展开,执行_Pragma(C99)运算。如果遇到#include预处理命令,则把相应的源文件包含进来,并对源文件反复做1~4步的预处理,直到源文件中没有预处理命令。

(5)把字符常量或字符串字面量中的转义序列替换为对应的字符。

(6)连接相邻的字符串字面量。比如把代码中的“"ABC" "DEF"”替换为“"ABCDEF"”。

此后,把预处理命令删除,就可以进行语法、词法分析从而转入编译阶段了。

下面详细讨论具体的编译预处理命令。