现在我们全面了解一下C编译器做语法解析之前的预处理步骤:
1、把第 2 节 “常量”提到过的三连符替换成相应的单字符。
2、把用\
字符续行的多行代码接成一行。例如:
#define STR "hello, "\ "world"
经过这个预处理步骤之后接成一行:
#define STR "hello, " "world"
这种续行的写法要求\
后面紧跟换行,中间不能有其它空白字符。
3、把注释(不管是单行注释还是多行注释)都替换成一个空格。
4、经过以上两步之后去掉了一些换行,有的换行在续行过程中去掉了,有的换行在多行注释之中,也随着注释一起去掉了,剩下的代码行称为逻辑代码行。然后预处理器把逻辑代码行划分成Token和空白字符,这时的Token称为预处理Token,包括标识符、整数常量、浮点数常量、字符常量、字符串、运算符和其它符号。继续上面的例子,两个源代码行被接成一个逻辑代码行,然后这个逻辑代码行被划分成Token和空白字符:#
,define
,空格,STR
,空格,"hello, "
,Tab,Tab,"world"
。
5、在Token中识别出预处理指示,做相应的预处理动作,如果遇到#include
预处理指示,则把相应的源文件包含进来,并对源文件做以上1-4步预处理。如果遇到宏定义则做宏展开。
我们早在第 2 节 “数组应用实例:统计随机数”就认识了预处理指示这个概念,现在给出它的严格定义。一条预处理指示由一个逻辑代码行组成,以#
开头,后面跟若干个预处理Token,在预处理指示中允许使用的空白字符只有空格和Tab。
6、找出字符常量或字符串中的转义序列,用相应的字节来替换它,比如把\n
替换成字节0x0a。
7、把相邻的字符串连接起来。继续上面的例子,如果代码中有:
printf( STR);
经过第4步处理划分成以下Token:printf
,(
,换行,Tab,STR
,)
,;
,换行。经过第5步宏展开后变成以下Token:printf
,(
,换行,Tab,"hello, "
,Tab,Tab,"world"
,)
,;
,换行。然后把相邻的字符串连接起来,变成以下Token:printf
,(
,换行,Tab,"hello, world"
,)
,;
,换行。
8、经过以上处理之后,把空白字符丢掉,把Token交给C编译器做语法解析,这时就不再是预处理Token,而称为C Token了。这里丢掉的空白字符包括空格、换行、水平Tab、垂直Tab、分页符。继续上面的例子,最后交给C编译器做语法解析的Token是:printf
,(
,"hello, world"
,)
,;
。注意,把一个预处理指示写成多行要用\
续行,因为根据定义,一条预处理指示只能由一个逻辑代码行组成,而把C代码写成多行则不需要用\
续行,因为换行在C代码中只不过是一种空白字符,在做语法解析时所有空白字符都已经丢掉了。