翻译阶段
来自cppreference.com
编译器如同下列阶段严格以此顺序进行一般来处理 C 源文件。实际的实现可以组合,或以不同方式处理这些动作,只要行为相同。
阶段 1
1) 以实现定义方式,将源文件(通常是以如 UTF-8 这样的某种多字节编码的文本文件)的各个字节,映射为源字符集的字符。特别是,以换行字符替换特定于 OS 的行尾指示符。
- 源字符集是包含作为单字节子集的基本源字符集的多字节字符集,后者由以下 96 个字符组成:
a) 5 个空白字符(空格、水平制表、垂直制表、换页、换行)
b) 10 个数字字符,从 '0' 到 '9'
c) 52 个字母,从 'A' 到 'Z' 以及从 'a' 到 'z'
d) 29 个标点字符: _ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '
2) 以对应的单字节表示替换三标符。 (C23 前)
阶段 2
1) 凡在反斜杠出现于行尾(紧跟换行符)时,删除反斜杠和换行符,把两个物理源码行组合成一个逻辑源码行。这是单趟操作:以两个反斜杠结束后随一个空行的行,不会把三行组合为一。
运行此代码
#include <stdio.h> #define PUTS p\ u\ t\ s /* 行拼接在阶段 2 进行, * 而宏的标记分析是在阶段 3 并在阶段 4 展开, * 因此以上代码等价于 #define PUTS puts */ int main(void) { /* 用行拼接来调用 puts */ PUT\ S\ ("Output ends here\\ 0Not printed" /* 行拼接之后,剩余的反斜杠 * 转义了 0,提早结束了字符串。 */ ); }
2) 若此步骤后,非空源文件不以换行符结束(无论是原本就无换行,还是以反斜杠结束),则行为未定义。
阶段 3
1) 将源文件分解为注释、空白字符(空格、水平制表、换行、垂直制表、换页)序列和下列预处理记号:
a) 头文件名:<stdio.h> 或 "myfile.h"
b) 标识符
e) 运算符与标点,例如 +、<<=、<% 或 ##。
f) 不属于任何其他类别的单独非空白字符
2) 以一个空格字符替换每段注释
3) 保持换行符。是否可将非换行的空白符序列缩减成单个空格字符是实现定义的。
若已经分析输入为到给定字符为止的预处理记号,则通常将能构成一个预处理记号的最长字符序列处理成下个预处理记号,即这会导致后继分析失败。这常被称为最大吞噬 (maximal munch)。
int foo = 1; // int bar = 0xE+foo; // 错误:非法的预处理数字 0xE+foo int bar = 0xE/*注释扩展为空格*/+foo; // OK: 0xE + foo int baz = 0xE + foo; // OK:0xE + foo int pub = bar+++baz; // OK:bar++ + baz int ham = bar++-++baz; // OK:bar++ - ++baz // int qux = bar+++++baz; // 错误:bar++ ++ +baz,不是 bar++ + ++baz int qux = bar+++/*挽救的注释*/++baz; // OK:bar++ + ++baz
最大吞噬规则的仅有例外是:
- 头文件名预处理记号仅在 #include 或 #embed (C23 起) 指令中,在 __has_include 和 __has_embed 表达式中 (C23 起),和 #pragma 指令中的实现定义位置形成。
#define MACRO_1 1 #define MACRO_2 2 #define MACRO_3 3 #define MACRO_EXPR (MACRO_1 <MACRO_2> MACRO_3) // OK:<MACRO_2> 不是头文件名
阶段 4
1) 执行预处理器。
2) #include 指令所引入的每个文件都经历阶段 1 到 4,递归执行。
3) 此阶段结束时,从源码移除所有预处理器指令。
阶段 5
1) 将字符常量及字符串字面量中的所有字符及转义序列从源字符集转换成执行字符集(可为如 UTF-8 的多字节字符集,只要来自阶段 1 中所列的基本源字符集的所有 96 个字符拥有单字节表示)。若转义序列所指定的字符不是执行字符集的成员,则结果是实现定义的,但保证不是空(宽)字符。
注意:某些实现中,能以命令行选项控制此阶段所进行的转换:gcc 和 clang 用 -finput-charset 指定源字符集的编码,用 -fexec-charset 和 -fwide-exec-charset 指定无编码前缀的 (C11 起)字符串字面量和字符常量中的执行字符集的编码。
阶段 6
连接相邻的字符串字面量。
阶段 7
发生编译:对各个记号进行语法和语义分析,并将它们作为翻译单元完成翻译。
阶段 8
发生链接:将翻译单元和满足外部引用所需的库组件到汇集成程序映像,它含有在其执行环境(操作系统)中执行所需的信息。
引用
- C23 标准(ISO/IEC 9899:2024):
- 5.1.1.2 Translation phases (第 TBD 页)
- 5.2.1 Character sets (第 TBD 页)
- 6.4 Lexical elements (第 TBD 页)
- C17 标准(ISO/IEC 9899:2018):
- 5.1.1.2 Translation phases (第 9-10 页)
- 5.2.1 Character sets (第 17 页)
- 6.4 Lexical elements (第 41-54 页)
- C11 标准(ISO/IEC 9899:2011):
- 5.1.1.2 Translation phases (第 10-11 页)
- 5.2.1 Character sets (第 22-24 页)
- 6.4 Lexical elements (第 57-75 页)
- C99 标准(ISO/IEC 9899:1999):
- 5.1.1.2 Translation phases (第 9-10 页)
- 5.2.1 Character sets (第 17-19 页)
- 6.4 Lexical elements (第 49-66 页)
- C89/C90 标准(ISO/IEC 9899:1990):
- 2.1.1.2 Translation phases
- 2.2.1 Character sets
- 3.1 Lexical elements