编译阶段
C程序的编译可分为以下8个步骤(注:实现可能会将多个步骤合并成1个步骤,但实现应表现为出现了8个这样的步骤。源文件、编译单元和编译过的编译单元不一定存储为文件,这些实现和外部表示之间也不需要一一对应。这里描述只是概念性的,并没有指定任何特定的实现。):
步骤1
如有必要,物理源文件中多字节字符以实现定义的方式映射到源字符集(使用换行符替换行尾指示符);三字符序列使用对应的单字符内部表示形式替换。
步骤2
如果反斜杠字符(\)后紧跟换行符,将删除反斜杠字符;将物理源代码行(physical source lines)拼接成逻辑源代码行(logical source lines),例如:
|
|
这里两个物理源代码行会拼接成一个逻辑源代码行。
只有物理源代码行中最后一个反斜杠字符才能成为此类拼接的一部分。非空源文件应以换行符结束;在进行任何此类拼接前,该换行符不能紧跟在反斜杠字符之后。
步骤3
源文件分解成预处理标记和空格字符序列(包括注释);其中预处理标记(preprocessing tokens)包括:头文件名、标识符、预处理数、字符常量、字符串字面量、标点符号、不属于前述类别的非空格字符。标准空格字符包括:空格符、换页符、换行符、回车符、水平制表符、垂直制表符。
将源文件分解成预处理标记时,预处理标记应为构成预处理标记的最长字符序列,例如:
A+++++B
将解释为A ++ ++ + B,而不是A ++ + ++ B;尽管前者是无效的C程序代码,而后者是有效的C程序代码。
源文件不能以部分预处理标记或者部分注释结束。每个注释将使用一个空格符替换;换行符将保留;除换行符外的空格字符构成的非空序列是保留,还是使用一个空格符替换,将由实现定义。
步骤4
进行预处理:执行预处理指令,扩展宏,并执行_Pragma一元运算符表达式。如果通过标记串联得到匹配通用字符名语法的字符序列,其行为是未定义的。使用#include预处理指令包含的头文件和源文件按步骤1至步骤4递归处理。该步骤结束后,将删除所有的预处理指令。
步骤5
字符常量和字符串字面量中的每个源字符集成员和转义序列将转换成对应的执行字符集成员;如果不存在对应的执行字符集成员,将转换成实现定义的除空字符(空宽字符)外的成员;但实现不需要将所有不存在对应执行字符集成员的源字符都转换成相同的执行字符。
步骤6
串联相邻的字符串字面量。
步骤7
进行编译:分隔标记的空格字符不再重要;每个预处理标记都转换成标记;对结果标记进行语法和语义分析,将其作为编译单元进行编译。
步骤8
进行链接:所有外部对象和函数引用都已解析。链接库组件,以满足不在当前编译单元中定义的函数和对象的外部引用。所有此类编译器输出都收集到程序映像(program image)中,该程序映像包含在执行环境中执行程序所需的信息。
这8个步骤是从C源代码到可执行文件的过程;在前4个步骤中,C预处理器为编译器准备好代码。