翻译阶段 (Phases of translation)
C 语言的编译器处理代码生成程序的过程,可以分为以下 8 个步骤,称为翻译阶段:
实现定义
在编译器的实现中,可以按需组合或者以不同方式进行这些步骤,但行为必须和这些步骤描述的一致。
1. 八个阶段的语义顺序
- 将源文件映射到源字符集并识别行结束。
- 删除反斜杠加换行形成的续行。
- 把源文件分解为预处理记号与空白序列,并把注释替换为空格。
- 执行预处理指令,展开宏并处理
#include与条件编译。 - 将字符常量与字符串字面量中的转义序列、通用字符名映射到执行字符集。
- 连接相邻字符串字面量。
- 把记号流翻译为翻译单元,完成语法语义分析并生成目标代码。
- 链接各翻译单元与库,解析外部符号,形成最终程序映像。
2. 工程含义
翻译阶段解释了三个常见现象:第一,宏替换先于语义分析,所以宏更像“记号替换”;第二,头文件不是独立编译单元,而是被插入包含它的源文件;第三,字符串字面量自动拼接是语言规则,不是编译器私有技巧。
#include <stdio.h>
#define APP_NAME "Mdr"
#define APP_VER " C23"
int main(void) {
puts(APP_NAME APP_VER);
return 0;
}2
3
4
5
6
7
8
9
可能的输出(示例):
<输出与输入或平台相关,请以实际运行为准>
上例中 APP_NAME APP_VER 会在翻译阶段 6 连接为一个字符串字面量,再进入后续语义阶段。掌握这一点,调试宏问题会清晰很多。
3. 三个常见误区
第一,预处理不是“运行一次脚本”,而是标准语义定义的一部分,结果会直接影响后续语法分析。第二,头文件不是独立编译单元,任何包含关系都会被展开到当前翻译单元中。第三,链接错误不等于语法错误,很多“编译成功但无法链接”的问题都出现在第 8 阶段。
4. 观察翻译结果的方法
遇到宏展开或包含链疑问时,先查看预处理后输出通常最直接。你会看到“编译器真正接收的记号流”,从而快速判断问题是出在预处理阶段,还是后续语义阶段。把排错切面建立在翻译阶段模型上,定位速度会明显提高。
5. 续行与注释的先后关系
翻译阶段 2 会先处理“反斜杠 + 换行”的续行,再在阶段 3 把注释替换为空格。这个顺序意味着:某些看似跨行的写法在预处理前就已经拼成一行。理解这点能解释不少“宏看起来没问题但展开结果异常”的现象。
#define MSG "hello" \
" world"2
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
上例在早期阶段就会拼成连续记号,后续再参与字符串字面量连接。若这里混入不当注释,最终记号流可能和直观看到的源码不同。
6. 把阶段模型用于错误分层
当出现“预处理报错、语法报错、链接报错”时,先按翻译阶段分类,再决定排查入口,效率会更高:宏条件错误优先看阶段 4,类型与语法错误优先看阶段 7,外部符号缺失优先看阶段 8。把问题归位到正确阶段,通常能避免在无关位置反复试错。