17.2.2 编译过程概述
编译过程是本章最核心的考点。记忆顺序不难,难的是把“这个错误归哪一阶段”判断清楚。
mermaid
flowchart LR
A["字符流/源程序"] --> B["词法分析"]
B --> C["Token 序列"]
C --> D["语法分析"]
D --> E["语法树/分析树"]
E --> F["语义分析"]
F --> G["中间代码"]
G --> H["代码优化"]
H --> I["目标代码"]
S["符号表管理"] -.贯穿.-> B
S -.贯穿.-> D
S -.贯穿.-> F
X["错误处理"] -.贯穿.-> B
X -.贯穿.-> D
X -.贯穿.-> F词法分析:字符变单词
词法分析器从源程序字符流中识别关键字、标识符、常量、运算符、界符等,输出 Token 序列。它关心的是“单词是否合法”,常用正规式和有限自动机实现。
| 输入 | 输出 | 典型错误 |
|---|---|---|
| 字符流 | Token 序列 | 非法字符、标识符拼写不合法、数字格式错误 |
语法分析:单词变结构
语法分析根据文法检查 Token 序列能否组成合法句子,并构造语法树或分析树。它关心的是“结构是否符合语法规则”。
| 输入 | 输出 | 典型错误 |
|---|---|---|
| Token 序列 | 语法树/分析树 | 缺少分号、括号不匹配、if 与 endif 不匹配、表达式缺少操作数 |
常见方法包括自顶向下分析和自底向上分析,移进-归约属于自底向上思想。
语义分析:结构有了,含义还要合理
语义分析检查类型、作用域、参数匹配、运算对象是否合法等。语法正确不代表语义正确,例如 int a; a = "hello"; 结构上可能像赋值语句,但类型不匹配。
| 类型 | 发现时机 | 例子 |
|---|---|---|
| 静态语义错误 | 编译期 | 运算符与操作数类型不匹配、未声明变量、参数个数不符 |
| 动态语义错误 | 运行期 | 除数运行时为 0、数组下标越界、死循环 |
课程里提到的“无限循环、变量为零导致除零、数组越界”更接近运行期问题,不能简单归到词法或语法。
中间代码与优化
中间代码是介于源语言和目标机器代码之间的表示。它让编译器先脱离具体机器分析程序,再在后续阶段生成不同平台的目标代码。
| 中间表示 | 特点 |
|---|---|
| 语法树 | 保留表达式和语句结构 |
| 后缀表达式 | 运算符在操作数之后,便于栈式处理 |
| 三地址码/四元式 | 每条语句最多涉及三个地址,利于优化 |
代码优化在不改变程序语义的前提下改善时间或空间效率。优化不是“让错误程序变正确”,而是让正确程序更好地执行。
错误定位口诀
| 问题表现 | 优先判断 |
|---|---|
| 单词本身不合法 | 词法错误 |
| 单词都合法,但句子结构不合法 | 语法错误 |
| 结构合法,但类型、作用域、参数等含义不合理 | 语义错误 |
| 只有运行时具体值才暴露 | 运行时错误/动态语义错误 |
小练习
题:a = b + ; 更可能属于哪类错误?
答:语法错误。标识符和运算符本身合法,但表达式结构缺少操作数。
自查
- 词法分析的输出为什么不是语法树?
- “括号不匹配”和“类型不匹配”分别属于什么层次?
- 为什么符号表管理要贯穿多个编译阶段?