编译原理(实验班)
期末一站式复习
覆盖 Lect01-15 全部考点,重点放在 Lect08-15 进阶内容。全部知识点来源课件,结合普通班 2016/2017/2018/2021 真题改编出更贴近实验班难度的练习。
🎯 实验班期末考试形式(来自 Lect15 复习讲)
必答题覆盖:自底向上语法分析(LR / SLR / LALR)、语法制导翻译方案(回填思想的三地址代码生成)、运行时环境(非局部数据访问、垃圾回收)、代码优化(公共子表达式提取、死代码消除……)。
选答题覆盖:静态单赋值(Koopa IR)的生成与优化、函数式语言的编译、类型系统与类型检查。
📊 考点权重(基于课件复习讲 + 真题统计)整理推断
LR / SLR / LALR 分析
FOLLOW 集、LR(0) 自动机、ACTION/GOTO 表(去年期末 20 分)
SDT + 回填
三地址代码生成、布尔短路回填、控制流(去年期末 20 分)
运行时 & 优化
访问链 / display、垃圾回收、活跃变量 + 图染色寄存器分配
SSA / 函数式 / 类型
Koopa IR 块参数、闭包转换、类型判断规则与可靠性(各 20 分)
🗺️ 期末考点路线图 整理导图
前端速览(Lect01-07)
RE/自动机、LL(1)、CFG、SDD、三地址码——期中已细讲,期末快速回顾
LR 分析进阶(Lect11)
项/部分分析状态 → NFA → 子集构造 → LR(0)/SLR/LR(1)/LALR · 必答
语义 & IR 进阶(Lect12-13)
S/L 属性的移进-归约实现、标记符号、回填、数组寻址、SSA 生成 · 必答 + 选答
运行时 & 代码生成(Lect06-07,14)
访问链/display、垃圾回收、SSA 弦图着色、线性扫描 · 必答 + 选答
代码优化(Lect08,14)
DAG、窥孔、数据流分析四要素、四大经典问题 · 必答
函数式 & 类型(Lect09-10)
闭包、KNF/ANF/CPS、闭包转换;类型规则、可靠性、多态、递归类型、GADT · 选答
第 1 章 · 前端速览(Lect01-07) 课件 Lect01-07
1.1 编译器流水线
- 前端(依赖源语言):词法、语法、语义;后端(依赖目标机器):代码生成、机器相关优化。
- 符号表管理与错误处理贯穿全流程。
1.2 词法:RE → NFA → DFA → 最小化
| 环节 | 关键点 |
|---|---|
| RE → NFA | Thompson 构造,引入 $\varepsilon$ 转移 |
| NFA → DFA | 子集构造($\varepsilon$ 闭包 + move),最坏状态数 $O(2^n)$ |
| DFA 最小化 | 初始划分必为「接受态 / 非接受态」两组,再按转移细分 |
1.3 自顶向下:LL(1)
消除左递归: $A \to A\alpha \mid \beta \Rightarrow A \to \beta A',\; A' \to \alpha A' \mid \varepsilon$
提取左公因子: $A \to \alpha\beta \mid \alpha\gamma \Rightarrow A \to \alpha A',\; A' \to \beta \mid \gamma$
LL(1) 条件:$A\to\alpha\mid\beta$ 要求 $\text{First}(\alpha)\cap\text{First}(\beta)=\varnothing$;若 $\varepsilon\in\text{First}(\alpha)$,还需 $\text{First}(\beta)\cap\text{Follow}(A)=\varnothing$。
1.4 语义:SDD / SDT
- 综合属性:自底向上(孩子 → 父);继承属性:自父或左兄弟流入。
- S 属性(仅综合)$\subsetneq$ L 属性(综合 + 受限继承,只依赖父和左兄弟)。
- S 属性可在 LR 归约时求值;L 属性在 LL/递归下降中一趟左→右求值。
A 错:NFA 与 DFA 表达能力相同。
B 错:LR 不需要消除左递归(这是 LR 相对 LL 的优势)。
C 对:S 属性(仅综合)$\subsetneq$ L 属性。
D 错:CFG 严格包含正则语言。
第 2 章 · LR 分析:项集族与分析表(Lect11) 课件 Lect11
2.1 自底向上分析的核心思想
- 自底向上分析构造最右推导的逆过程:从左到右读入 token,在分析栈顶识别可归约的「模式」并归约。
- 两类动作:移进 shift(把一个 token 压入分析栈)、归约 reduce(把栈顶若干符号归约为非终结符)。
- 句柄 handle:当前最右句型中、可被某产生式右部归约的子串,总是出现在栈顶。
2.2 项(item)= 部分分析状态
用圆点 $\bullet$ 标记「已分析到哪里」,圆点左边是已进栈内容:
| 项 | 含义 |
|---|---|
| $A \to \bullet\,\alpha$ | 准备开始分析 $\alpha$(栈顶可移进 $\text{First}(\alpha)$) |
| $A \to \alpha\bullet c\,\gamma$ | 下一步移进终结符 $c$ |
| $A \to \alpha\bullet B\,\gamma$ | 对 $B$ 的每条产生式 $B\to\delta$ 加入 $B\to\bullet\,\delta$($\varepsilon$ 闭包) |
| $A \to \alpha\,\bullet$ | 可归约状态(栈顶 $\alpha$ 归约为 $A$) |
2.3 构造识别活前缀的 DFA
把所有项作为 NFA 状态:
- 对 $A\to\alpha\bullet c\gamma$,经 $c$ 转移到 $A\to\alpha c\bullet\gamma$(移进)。
- 对 $A\to\alpha\bullet X\gamma$($X$ 非终结符):① 经 $X$ 转移到 $A\to\alpha X\bullet\gamma$;② 对每条 $X\to\delta$,经 $\varepsilon$ 转移到 $X\to\bullet\delta$。
- 再用子集构造法($\varepsilon$ 闭包 + 转移)把 NFA 化为 DFA,得到规范 LR(0) 项集族。
2.4 走通一个完整例子:括号文法
增广文法(无二义版本):
[0] S' → S EOF [1] S → ε [2] S → [ S ] S
构造 LR(0) 项集族(部分状态):
对上面括号文法,构造 LR(0) 项集族 $I_0 \sim I_n$,并据 $\text{Follow}(S)=\{\,]\,,\text{EOF}\}$ 构造 SLR(1) 分析表。
项集族
| 状态 | 项(闭包后) | 转移 |
|---|---|---|
| $I_0$ | $S'\to\bullet S\,\text{EOF}$;$S\to\bullet$;$S\to\bullet [S]S$ | $S\!\to\!I_1$,$[\,\to\!I_2$ |
| $I_1$ | $S'\to S\bullet\text{EOF}$ | $\text{EOF}\to$ 接受 |
| $I_2$ | $S\to[\bullet S]S$;$S\to\bullet$;$S\to\bullet[S]S$ | $S\!\to\!I_3$,$[\,\to\!I_2$ |
| $I_3$ | $S\to[S\bullet]S$ | $]\to I_4$ |
| $I_4$ | $S\to[S]\bullet S$;$S\to\bullet$;$S\to\bullet[S]S$ | $S\!\to\!I_5$,$[\,\to\!I_2$ |
| $I_5$ | $S\to[S]S\bullet$ | —(归约 [2]) |
SLR(1) 分析表
含 $S\to\bullet$ 的状态($I_0,I_2,I_4$)对 $\text{Follow}(S)=\{\,]\,,\text{EOF}\}$ 填归约 [1];$I_5$ 对 $\text{Follow}(S)$ 填归约 [2]。
| 状态 | $[$ | $]$ | EOF | GOTO S |
|---|---|---|---|---|
| $I_0$ | s2 | r1 | r1 | 1 |
| $I_1$ | acc | |||
| $I_2$ | s2 | r1 | r1 | 3 |
| $I_3$ | s4 | |||
| $I_4$ | s2 | r1 | r1 | 5 |
| $I_5$ | r2 | r2 |
无冲突 ⇒ 该文法是 SLR(1)。
FOLLOW 集是「对所有上下文」求的,太粗糙。课件经典反例 $S\to abd\mid aAc\mid bAd,\;A\to b$:$\text{Follow}(A)=\{c,d\}$,栈顶 $ab$ 遇 $d$ 既想归约 $A\to b$ 又想移进,但实际归约后只有 $S\to aAc$ 适用(只允许 $c$)→ 须用更精确的「再向前看」(LR(1))。
C 错:SLR(1) 与 LR(0) 项集族完全相同。D 错:SLR 用的是 FOLLOW,不是精确展望符。
第 3 章 · LR(0)/SLR/LALR/LR(1) 辨析(Lect11) 课件 Lect11
3.1 四种 LR 分析对照表(必背)
| 变种 | 自动机状态 | 归约时的展望符 | 能力 |
|---|---|---|---|
| LR(0) | $\langle A\to\alpha\bullet\gamma\rangle$ | 不使用展望符,对所有终结符归约 | 最弱 |
| SLR(1) | 与 LR(0) 相同 | 用 $\text{Follow}(A)$ 确定 | 中 |
| LALR(1) | 与 LR(0) 同构(合并同心项集) | 每项带精确展望符,合并后 | 强 |
| LR(1) | $\langle A\to\alpha\bullet\gamma,\,c\rangle$ | 每项各带精确展望符 $c$ | 最强 |
能力关系: $\text{LR}(0) \subsetneq \text{SLR}(1) \subsetneq \text{LALR}(1) \subsetneq \text{LR}(1)$;同时 $\text{LL}(k) \subsetneq \text{LR}(k)$。
状态数: $\text{LALR}(1)$ 状态数 $=$ $\text{LR}(0)$ 状态数(远少于 LR(1)),这是其实用价值的关键。
3.2 LR(1) 闭包的展望符传播规则(必考)
对项 $[A\to\alpha\bullet B\beta,\;a]$ 和产生式 $B\to\gamma$,加入 $[B\to\bullet\gamma,\;b]$,其中 $b\in\text{First}(\beta a)$。
若 $\beta$ 能推出 $\varepsilon$,则 $b$ 包含 $a$。
3.3 冲突类型与 LALR 的陷阱
| 冲突 | LR 表中能否出现 | 说明 |
|---|---|---|
| 移进/归约 (S/R) | 可能 | 同状态既要移进又要归约 |
| 归约/归约 (R/R) | 可能 | 同状态两个归约项展望符重叠 |
| 移进/移进 (S/S) | 不可能 | 移进由 GOTO 唯一决定 |
课件反例:$S\to aAd\mid bBd\mid aBe\mid bAe,\;A\to c,\;B\to c$,栈顶 $ac$/$bc$ 合并后无法区分归约 $A\to c$ 还是 $B\to c$ → 归约/归约冲突。
3.4 「向前看」vs「再向前看」
- LL(k):向前看 $k$ 个符号来决定用哪条产生式推导(展开前就要猜)。
- LR(k):移进时向前看 1 个符号;归约时看到完整可归约子串后,再向前看 $k$ 个符号决定用哪条规则归约。
- 因此 LR 看到的信息更多 → 表达能力更强。
判断文法 $S \to Aa \mid bAc \mid dc \mid bda$;$A \to d$ 是否为 SLR / LALR / LR(1)。
分析读到 $d$ 后的状态
• 路径 $S\to bAc$:$d$ 由 $A$ 推出,期望后跟 $c$ → $[A\to d\bullet, c]$
• 路径 $S\to dc$:直接 $[S\to d\bullet c]$(移进 $c$)
• 路径 $S\to bda$:$[S\to bd\bullet a]$
SLR(1):$\text{Follow}(A)=\{a,c\}$。含 $[A\to d\bullet]$ 的状态对 $a,c$ 都归约,但同状态有 $[S\to d\bullet c]$ 想移进 $c$ → 移进/归约冲突,非 SLR。
LR(1):$[A\to d\bullet,a]$ 与 $[S\to d\bullet c,\$]$ 在不同状态(展望符不同 → 路径不同 → 状态不同),无冲突。
LALR(1):合并同心项集后,$[A\to d\bullet,a]$、$[A\to d\bullet,c]$、$[S\to d\bullet c]$ 进入同一状态:对 $c$ 既归约又移进 → 冲突。
结论:仅是 LR(1) 文法。
第 4 章 · SDD / SDT 与移进-归约实现(Lect12) 课件 Lect12
4.1 SDD 与 SDT
- SDD(语法制导定义)= 属性文法:为产生式关联属性计算规则,不指定计算顺序。
- SDT(语法制导翻译方案):把属性计算改写为程序片段,用
{ }插入产生式右部任意位置,明确了求值顺序(深度优先遍历分析树时何时执行动作)。 - 后缀翻译方案:所有语义动作都在产生式最右侧,适合 S 属性文法在 LR 归约时执行。
4.2 中缀转前缀(副作用 SDT 经典例)
输入 9+5*2,输出 + 9 * 5 2:
Expr → { print('+'); } Expr1 + Term
Term → { print('*'); } Term1 * Factor
Factor → INT { print(INT.lexeme); }
4.3 S 属性的移进-归约实现
分析栈中为每个非终结符记录综合属性;归约 $A\to B_1\cdots B_k$ 时按栈顶 $k$ 个符号属性计算 $A$ 的属性。如括号计数:
[0] S' → S EOF { S'.cnt = S.cnt; }
[1] S → ε { S.cnt = 0; }
[2] S → [S1] S2 { S.cnt = 1 + S1.cnt + S2.cnt; }
显式操作分析栈版本:stack[top-3].cnt = 1 + stack[top-2].cnt + stack[top].cnt; top = top-3;
4.4 L 属性的移进-归约实现:标记非终结符 ★难点
解决条件:当文法本身是 LL 的,自底向上移进时已有足够信息确定展开规则,于是以 LL 文法为基础的 L 属性 SDD 可用移进-归约实现。
方法:插入标记非终结符 $L,M,N$(产生式右部为 $\varepsilon$),在归约位置「下方」的固定偏移处存放继承属性。改写后文法变为 LR 文法。
S' → L S EOF
L → ε { L.i = 0; }
S → [ M S1 ] N S2 { S.s = S2.s; }
M → ε { M.i = 栈中归约位置减2处.i + 1; }
N → ε { N.i = 栈中归约位置减2处.s; }
显式栈操作示例:M→ε: stack[top+1].i = stack[top-1].i + 1; top = top+1;
4.5 LL / LR × S / L 属性 支持矩阵
| 分析方法 | S 属性 | L 属性 |
|---|---|---|
| LL(递归下降 / 表驱动) | ✅ | ✅ |
| LR(移进-归约) | ✅ | ✅ 仅当以 LL 文法为基础(需标记符号) |
只有以 LL 文法为基础的 L 属性 SDD 才能用移进-归约实现,需借助标记非终结符在栈中固定位置安放继承属性。并非任意 L 属性都可以。
第 5 章 · 回填与控制流(Lect13) 课件 Lect13
5.1 回填(backpatching)思想
- 短路求值时跳转目标在生成跳转指令时尚未确定;回填记录这些指令位置(指令数组下标),暂不填目标,待目标确定后回过头填上 → 一趟完成。
- 三个辅助函数:
makelist(i)建只含指令 $i$ 的列表;merge(p1,p2)合并;backpatch(p,i)把列表 $p$ 中所有跳转目标填为 $i$。
5.2 布尔表达式:truelist / falselist
B → true: B.truelist = makelist(nextinstr); emit("goto _");
B → E1==E2: B.truelist = makelist(nextinstr); emit("if E1==E2 goto _");
B.falselist = makelist(nextinstr); emit("goto _");
B → B1 && B2: backpatch(B1.truelist, nextinstr);
B.truelist = B2.truelist;
B.falselist = merge(B1.falselist, B2.falselist);
B → B1 || B2: backpatch(B1.falselist, nextinstr);
B.truelist = merge(B1.truelist, B2.truelist);
B.falselist = B2.falselist;
B → !B1: B.truelist = B1.falselist; B.falselist = B1.truelist;
5.3 控制流语句:nextlist
S → if(B) S1: backpatch(B.truelist, nextinstr);
S.nextlist = merge(B.falselist, S1.nextlist);
S → while(B) S1: backpatch(S1.nextlist, S1.begin);
backpatch(B.truelist, nextinstr);
emit("goto S1.begin"); S.nextlist = B.falselist;
从指令编号 100 开始,对 x < 100 || (x > 200 && x != y) 生成三地址码并给出最终 B.truelist / B.falselist。
100: if x < 100 goto _ ; 左 B 的 truelist 101: goto _ ; 左 B 的 falselist 102: if x > 200 goto _ ; 进入 && 左部 103: goto _ 104: if x != y goto _ 105: goto _
处理 x>200 && x!=y:backpatch({102}, 104)(左真则测右)。该子式 truelist={104},falselist=merge({103},{105})={103,105}。
处理 ||:backpatch(左falselist={101}, 102)(左假才测右)。
最终: B.truelist = merge({100}, {104}) = {100, 104};B.falselist = {103, 105}。
5.4 回填法 vs CFG 直接生成法
| 回填法 | CFG / 基本块直接生成 | |
|---|---|---|
| 跳转目标 | 综合属性 truelist/falselist(先记后填) | 继承属性 B.true / B.false(提前生成目标块) |
| 辅助 | makelist / merge / backpatch | new_bb() / set_bb() / emit() |
| 输出 | 带回填的三地址码序列 | 控制流图(基本块 + 跳转) |
第 6 章 · 数组寻址与类型声明(Lect13) 课件 Lect13
6.1 寻址公式(必背)
一维:$\text{addr}(A[i]) = base + i\times w$
二维:$\text{addr}(A[i_1][i_2]) = base + i_1\times w_1 + i_2\times w_2$,其中 $w_2=w$,$w_1=n_2\times w$
$k$ 维按行存放:$\text{addr} = base + (\cdots((i_1 n_2 + i_2)n_3 + i_3)\cdots)n_k + i_k)\times w$
下标非 0 起(Pascal $[low..high]$):$\text{addr}(a[i]) = base + (i-low)\times w$
6.2 类型宽度计算
数组类型用继承属性 $t$(元素类型)、$w$(元素宽度);声明 int[2][3]:
- type = $\text{array}(2,\text{array}(3,\text{int}))$
- width = $2\times 3\times 4 = 24$(int 宽度 4)
6.3 结构体对齐(真题高频)
设 int a[10][20](int 宽度 4,按行存放),写出 x = a[i][j] 的三地址码。
t1 = i * 20 ; 行偏移(元素个数) t1 = t1 + j ; 总元素下标 t2 = t1 * 4 ; 字节偏移(w = 4) x = a[t2] ; 取值
地址 $= base + (i\times 20 + j)\times 4$,与 $k$ 维按行公式一致。
第 7 章 · 静态单赋值 SSA(Lect13) 课件 Lect13 · 选答
7.1 SSA 概念
三地址码 p=a+b; q=p-c; p=q*d; → SSA p1=a+b; q1=p1-c; p2=q1*d;
7.2 合并不同路径定值的两种设计
| 设计 | 形式 | 代表 |
|---|---|---|
| φ 函数 | $x_3 = \phi(x_1, x_2)$ | LLVM IR |
| 块参数 | L(z): y = z*a;,跳转写 goto L(x1) | Koopa IR |
7.3 入门生成法:alloc / load / store
为每个程序变量开辟内存(alloc),用 load 读、store 写,使变量对应唯一内存位置:
S → T ID;: emit("genvar(ID) = alloc");
E → ID: E.addr = gentmp(); emit("E.addr = load genvar(ID)");
S → ID=E;: emit("store E.addr genvar(ID)");
优化:符号表记录变量当前值的地址,尽量推迟写回内存(→ local / global value numbering)。
7.4 高质量 SSA 生成(块参数 / Koopa IR)
- 两趟法:第一趟每个基本块以所有变量为参数、块内做 local value numbering 转 SSA;第二趟若某参数 $x_i$ 在所有跳转处都等于同一个 $x_k$,则移除该参数并把 $x_i$ 替换为 $x_k$。
- 一趟法:块内 local value numbering;变量使用不在块内时看前驱——单前驱递归处理前驱,多前驱则添加该变量为块参数并递归传实参。
把 r=1; while(e>0){ r=r*b; e=e-1; } return r; 转为块参数形式的 SSA(Koopa 风格)。
entry: goto loop(e0, 1) ; r 初值 1 loop(e1, r1): t = e1 > 0 if t goto body(e1, r1) else goto done(r1) body(e2, r2): r3 = r2 * b e3 = e2 - 1 goto loop(e3, r3) ; 回边传递新值 done(r4): return r4
每个名字($e_1,r_1,r_3,\dots$)都只定值一次;循环汇合点用块参数合并来自 entry 与 body 的不同定值(等价于 $\phi$ 函数)。第二趟可消去 loop 中始终不变的参数(如本例 $b$ 未作参数)。
第 8 章 · 非局部数据访问与垃圾回收(Lect06,14) 课件 Lect06,14 · 必答
8.1 静态作用域 vs 动态作用域
| 静态(词法)作用域 | 动态作用域 | |
|---|---|---|
| 非局部名字绑定确定时机 | 过程被定义时 | 过程被调用时 |
| 实现 | 访问链 / display | 运行时为每个名字维护作用域栈 |
| 代表 | 多数语言 | Emacs Lisp |
8.2 访问链(access link)
- 嵌套深度:若 p 定义在深度 $i$ 的过程中,则 p 深度为 $i+1$。沿访问链走一步使深度恰好少一。
- 建立(深度 $m$ 的 q 调用深度 $n$ 的 p):若 $m \lt n$(必 $m+1=n$),p 的访问链指向 q 的活动记录;若 $m\ge n$,沿 q 的访问链走 $m-n+1$ 步找到目标活动记录。
- 访问(深度 $m$ 访问深度 $n$,$n\le m$):沿访问链走 $m-n$ 步,步数编译期确定。
8.3 显示表(display)
数组 $d$,$d[i]$ 指向最近的深度为 $i$ 的活动记录 → 访问开销为常数。调用深度 $n$ 的 p 时保存原 $d[n]$ 并令其指向 p 的活动记录,返回时恢复。
8.4 过程作为参数 / 结果 → 闭包
- 过程作参数:传递时同时传其访问链。
- 过程作结果返回:栈式活动记录管理不再适用(活动记录可能已出栈)→ 用闭包,在堆上存储所需外层局部数据。
- Lua 的 upvalue:初始指向栈中数据,若逃逸则转移到堆。
8.5 垃圾回收
| 方法 | 机制 | 优点 | 缺点 |
|---|---|---|---|
| 引用计数 | 每对象计数,赋值时增减,归零即回收 | 增量、无长停顿、及时 | 空间/时间代价、循环引用泄漏 |
| 标记-清扫 | 从根集遍历标记可达,再清扫不可达 | 正确处理循环、几乎无空间代价 | 全面停顿、堆碎片 |
| 标记-压缩 | 标记 + 重定位消除碎片 | 消碎片 | 需更新所有指针 |
| 拷贝回收 | From/To 半空间,拷贝可达对象后角色互换 | 无需扫整堆压缩 | 仅用一半空间 |
| 世代回收 | 按对象年龄分代,年轻代更频繁回收 | 利用「大多对象短命」 | 实现复杂 |
A 错:无法回收循环引用(计数永不归零)。B 错:引用计数是增量式,不需扫整堆(那是标记-清扫)。C 对:删除对象前要把它指向的对象计数各 −1(可能级联回收)。D 错:每次指针更新都要改计数,有时间/空间开销。
第 9 章 · 寄存器分配与弦图(Lect07,14) 课件 Lect07,14
9.1 图着色寄存器分配(真题骨架题)
- 活跃变量分析(后向):$\text{IN}[B]=\text{use}_B\cup(\text{OUT}[B]-\text{def}_B)$,$\text{OUT}[B]=\bigcup_{S\in succ}\text{IN}[S]$。
- 冲突图(干扰图):同一程序点同时活跃的两变量连边。
- k 着色:反复移除度 $\lt k$ 的结点入栈(simplify),再回填上色;无法移除时溢出 spill。
9.2 SSA → 弦图(选答进阶)
关键结论 [Hac07]:SSA IR 产生的冲突图都是弦图;实践中约 95% 冲突图是弦图。
- 单纯点:邻居构成一个团的点。单纯消除序列:每个 $v_i$ 是子图 $\{v_1,\dots,v_i\}$ 中的单纯点。
- 定理:图有单纯消除序列 $\iff$ 它是弦图。按单纯消除序列着色得最优方案,复杂度 $O(|V|+|E|)$。
- 合并 coalescing:在弦图上先着色后合并(合并可能破坏弦图性质,故顺序为「构图→单纯消除序列→着色→合并」)。
9.3 线性扫描寄存器分配(LSRA)
- 为每个符号寄存器计算活性区间 live interval;区间重叠的不能同寄存器。
- 维护按区间终点排序的 active 队列;扫到新区间起点时移除过期区间、加入新区间。复杂度 $O(|V|\log|R|)$。
- 关于代码大小线性,效果接近图着色 → LLVM、JVM JIT 默认采用(JIT 看重编译速度)。
SSA 冲突图是弦图,借单纯消除序列可多项式时间最优着色。B 错:一般图着色 NP 难。C 错:线性扫描更快但效果接近而非一定优于图着色。D 错:弦图上合并可能破坏弦图性质,故须先着色后合并。
第 10 章 · 局部优化 / DAG / 窥孔(Lect08) 课件 Lect08 · 必答
10.1 常用优化方法
| 方法 | 说明 |
|---|---|
| 公共子表达式消除 | 复用已算且分量未变的表达式 |
| 复写传播 | $x=y$ 后把用 $x$ 处换成 $y$,最终删 $x=y$ |
| 死代码消除 | 删除计算结果永不被用的语句 |
| 常量传播 / 折叠 | 编译期已知常量则代入 / 表达式求值(如 $3\times2\to6$) |
| 代码外提 LICM | 循环不变式提到循环前只算一次 |
| 强度消减 | 用加减替代乘法(归纳变量 $4j$ 每轮 $-4$) |
| 归纳变量消除 | 步调一致的归纳变量合并(如 $i\ge j$ 改 $t_2\ge t_4$) |
10.2 基本块 DAG
- 变量结点表初值;语句 $x=y\,op\,z$ 建标号 $op$ 的结点,孩子为 $y,z$ 当前关联结点,令 $x$ 关联 $N$。
- 复写 $x=y$ 不建新结点,$x$ 关联到 $y$ 的结点。
- 建结点前查找相同运算符 + 相同孩子(顺序相同)的已有结点 → 复用(局部公共子表达式消除)。
- 消死代码:删没有附加活跃变量的根结点;代数恒等式:$x+0=x$、$x\times2=x+x$ 等。
x=a[i]; a[j]=y; z=a[i] 中 a[i] 与 a[j] 可能别名,a[j]=y(运算符 []=)杀死所有依赖 a 的结点,故 z 须重算。指针 *q=y、过程调用同理(保守假设杀死/使用所有可访问数据)。
10.3 窥孔优化(四类)
| 类别 | 例子 |
|---|---|
| 冗余指令消除 | LD R1,a; ST a,R1 中第二条多余;不可达代码消除 |
| 控制流优化 | goto L1 … L1:goto L2 → goto L2 |
| 代数化简 | LD R2,#0; ADD R3,R1,R2 → LD R3,R1 |
| 机器特有指令 | LD R2,#1; ADD R1,R1,R2 → INC R1 |
t = 4 * j(循环内 j 每轮减 1)改为循环外 t = 4*j、循环内 t = t - 4,属于?$j$ 是归纳变量(每轮 −1),故 $4j$ 每轮 −4,用加减替代乘法即强度消减,但需在循环外对 $t$ 初始化。
第 11 章 · 数据流分析框架(Lect08) 课件 Lect08 · 必答
11.1 框架四要素
- 域 domain:数据流值集合(变量集 / 定值集 / 表达式集)。
- 方向:前向($\text{OUT}[B]=f_B(\text{IN}[B])$,IN 由前驱汇合)/ 后向($\text{IN}[B]=f_B(\text{OUT}[B])$,OUT 由后继汇合)。
- 传递函数 $f_B$:基本块入口与出口数据流值的关系,由语句传递函数复合得到。
- 交汇运算 $\wedge$:$\cup$(may,存在路径满足)/ $\cap$(must,所有路径满足)。
用顶值 $\top$ 初始化,迭代套用方程直到不动点(没有基本块需更新)。
11.2 四大经典数据流问题对照表(必背)
| 活跃变量 | 可达定值 | 可用表达式 | |
|---|---|---|---|
| 域 | 变量集合 | 定值集合 | 表达式集合 |
| 方向 | 后向 | 前向 | 前向 |
| 传递函数 | $(O-\text{def}_B)\cup\text{use}_B$ | $(I-\text{kill}_B)\cup\text{gen}_B$ | $(I-\text{ekill}_B)\cup\text{egen}_B$ |
| 交汇 | $\cup$(may) | $\cup$(may) | $\cap$(must) |
| 边界 / 初值 | $\text{IN}[\text{EXIT}]=\varnothing$ | $\text{OUT}[\text{ENTRY}]=\varnothing$ | $\text{OUT}[\text{ENTRY}]=\varnothing$,其余 $\top=$ 全集 |
| 用途 | 寄存器分配 / 死代码 | 常量传播 | 公共子表达式消除 |
11.3 变量符号分析(前向例)
符号格:正 / 零 / 负 / 槑(=所有数,表示「未知」)。交汇 $v_1\wedge v_2=v_1$(若相等),否则 $=$ 槑。基本块传递函数 $f_B=f_{s_n}\circ\cdots\circ f_{s_1}$(前向复合顺序)。
对下列分析,分别指出方向(前向/后向)与交汇运算($\cup$/$\cap$):①活跃变量 ②可用表达式 ③可达定值 ④忙表达式。
| 分析 | 方向 | 交汇 | 记忆口诀 |
|---|---|---|---|
| 活跃变量 | 后向 | $\cup$ (may) | 「未来可能被用」→ 任一后继用即活 |
| 可用表达式 | 前向 | $\cap$ (must) | 「所有路径都算过」→ 取交 |
| 可达定值 | 前向 | $\cup$ (may) | 「某条路径可达」→ 取并 |
| 忙表达式 | 后向 | $\cap$ (must) | 「所有后继都会算」→ 后向取交 |
记忆:活跃/忙后向;可达/可用前向。must(all paths)取交,may(some path)取并。
第 12 章 · 函数式语言的编译(Lect09) 课件 Lect09 · 选答
12.1 环境求值与闭包
- 环境 $\rho$:维护「自由变量 → 值」的绑定;$[x\mapsto v]\rho$ 扩展,后绑定覆盖先绑定。
- 求值三元关系 $\rho \vdash E \Rightarrow v$:在环境 $\rho$ 下对 $E$ 求值得 $v$。
- 静态(词法)作用域:函数体里的自由变量用定义时的环境,不是调用时。
- 闭包 closure $=(f, x, E_1, \rho)$:记录函数定义时「当时」的环境。这是高阶函数(参数生命周期超过定义它的函数)能正确求值的关键。
let 规则: $\dfrac{\rho\vdash E_1\Rightarrow v_1\quad [x\mapsto v_1]\rho\vdash E_2\Rightarrow v}{\rho\vdash \text{let}(x,E_1,E_2)\Rightarrow v}$
带闭包的 call: 取出闭包记录的 $\rho'$,在 $[x'\mapsto v_1,\,f'\mapsto\text{闭包}]\rho'$ 下求值函数体。
let f x = let g y = x+y in let x=42 in g(2*x) end end in f 7 end 结果是 91:g 体里的 x 用 g 定义时的 7(不是后来的 42),$g(2\times42)=g(84)=7+84=91$。12.2 三种正规形式(IR)对照
| 正规形式 | 约束 | 转换复杂度 |
|---|---|---|
| KNF(K-Normal Form) | 运算分量、分支条件不嵌套;let 可随意嵌套 | 简单 |
| ANF(A-Normal Form) | 运算分量、分支条件、let...in 中间部分都不嵌套 | 中 |
| CPS(Continuation-Passing Style) | 不嵌套;只有尾递归;每个函数加回调参数 | 复杂 |
12.3 CPS 变换(必会手写)
// 阶乘:递归构造新回调
fac(n,ret){ if(n==0) ret(1);
else fac(n-1, t1 => ret(n*t1)); }
// 尾递归阶乘:直接传 ret,不引入新回调
tail_fac(n,a,ret){ if(n==0) ret(a); else tail_fac(n-1, n*a, ret); }
// 斐波那契:两处递归 → 嵌套两层回调
fib(n,ret){ if(n<2) ret(1);
else fib(n-1, t1 => fib(n-2, t2 => ret(t1+t2))); }
CPS ↔ SSA/Koopa:尾递归函数调用就像控制流图上的带参 goto,回调对应一个基本块。
12.4 闭包转换(Closure Conversion)
目标:转换后所有函数都是「最外层」函数 @ℓ,并显式构造闭包。对每个 letfun:
- 生成新函数标记
@ℓ; - 分析函数体的自由变量作为闭包参数 $y_1,\dots,y_m$;
- 最外层生成
fun @ℓ {y1..ym} x = E1; - 原位置改为
let f = clos @ℓ {y1..ym} in E2。
运行时表示:闭包 = { func: 函数指针, env: 闭包环境 }。优化:若被调函数没有自由变量,就不必创建闭包。
闭包转换的优化之一:若已知具体调用函数且该函数没有自由变量,就不用创建闭包。其余 A/B/C 均正确。
第 13 章 · 类型系统(Lect10) 课件 Lect10 · 选答
13.1 类型与类型判断
- 类型是对程序行为的简洁、准确的形式化描述。「Well-typed expressions do not go wrong」(Milner)。
- 类型判断三元关系 $\Gamma \vdash E : T$:在类型上下文 $\Gamma$(变量→类型)下,$E$ 的类型是 $T$。
- 简单类型:$T ::= \text{int} \mid \text{bool} \mid T_1 \to T_2$。
- 静态类型缺点:可靠、可判定的类型系统一定会拒绝一些实际安全的程序。
13.2 类型规则(必背形式)
$\dfrac{}{\Gamma\vdash \text{const}(n):\text{int}}\qquad \dfrac{}{\Gamma\vdash \text{var}(x):\Gamma(x)}$
$\dfrac{\Gamma\vdash E_1:\text{bool}\quad \Gamma\vdash E_2:T\quad \Gamma\vdash E_3:T}{\Gamma\vdash \text{if}(E_1,E_2,E_3):T}$(两分支同类型)
$\dfrac{\Gamma\vdash E_1:T_1\quad \Gamma;x:T_1\vdash E_2:T_2}{\Gamma\vdash \text{let}(x,E_1,E_2):T_2}$
$\dfrac{\Gamma\vdash E_1:T_1\to T_2\quad \Gamma\vdash E_2:T_1}{\Gamma\vdash \text{call}(E_1,E_2):T_2}$(函数应用)
13.3 类型可靠性(type soundness)★难点
- 证明方法:对求值推导 $\rho\vdash E\Rightarrow v$ 做结构归纳。
- 关键技巧——强化命题:直接对原命题归纳会在 let / 函数体处失败,须强化为:「若 $\Gamma\vdash E:T$、$\rho\vdash E\Rightarrow v$ 且 $\rho:\Gamma$(环境与上下文一致),则 $v:T$」。
- 闭包定型:$\dfrac{\rho:\Gamma\quad \Gamma,f:T_1\to T_2,x:T_1\vdash E:T_2}{\text{closure}(f,x,E,\rho):T_1\to T_2}$。
13.4 多态
| 类型 | 含义 | 例 |
|---|---|---|
| 参数多态 | 不同类型上行为相同 | C++ template、Rust 泛型、$\forall X.\,T$ |
| 特设多态 | 不同类型上行为不同 | 运算符重载、Rust trait、Haskell typeclass |
| 子类型多态 | OO 中的子类型 | 继承 |
多态调用是显式类型实参代入:$\dfrac{\Gamma\vdash E_1:\forall\vec X.T_1\to T_2\quad \Gamma\vdash E_2:T_1[\vec T/\vec X]}{\Gamma\vdash \text{call}(E_1,\langle\vec T\rangle,E_2):T_2[\vec T/\vec X]}$。运行时可做类型擦除。
13.5 乘积 / 加和 / 递归类型
乘积:$\dfrac{\Gamma\vdash E_1:T_1\quad \Gamma\vdash E_2:T_2}{\Gamma\vdash \text{pair}(E_1,E_2):T_1\times T_2}$;单位元 $1$(unit)。$1\times T\simeq T$。
加和:$\dfrac{\Gamma\vdash E_1:T_1+T_2\quad \Gamma\vdash E_2:T_1\to T\quad \Gamma\vdash E_3:T_2\to T}{\Gamma\vdash \text{case}(E_1,E_2,E_3):T}$;单位元 $0$。$0+T\simeq T$。
递归:$\text{nat}\simeq 1+\text{nat}$,$\text{list}(X)\simeq 1+X\times\text{list}(X)$。iso-recursive 用 fold/unfold:$\dfrac{\Gamma\vdash E_1:T[(\mu X.T)/X]}{\Gamma\vdash \text{fold}(E_1):\mu X.T}$。
13.6 GADT(泛化代数数据类型)
GADT = 允许 ADT 不同分支有不同的类型参数。经典例:良类型的 eval : 't exp -> 't value(求值无需运行时检查)、良类型的 printf : 'a desc -> 'a(参数个数/类型由格式串决定仍保证良类型)。
A 错:可靠可判定一定会拒绝一些实际安全的程序。B 错:须强化命题(引入 $\rho:\Gamma$)才能归纳成功。C 对:该方程无穷展开,简单类型无解,需递归类型。D 错:行为相同才是参数多态,行为不同是特设多态。
第 14 章 · 期末真题精选(普通班)真题改编
给定文法,构造 LR(0) 项集自动机,指出冲突状态及类型,并证明该文法是 SLR(1)(即所有冲突均可由 FOLLOW 集消解)。
解题套路
关键是写清楚每个冲突状态的判断依据,而非只给结论。
给定一段含 if / while 的程序,按回填技术生成三地址码,求各语句 $S_6\sim S_{11}$ 的 nextlist。
解题套路
if(B)S1:backpatch(B.truelist, S1首条),S.nextlist = merge(B.falselist, S1.nextlist);③ while(B)S1:循环体末尾回跳测试入口,S.nextlist = B.falselist;④ 逐条记录指令编号,最后合并列表。
注意「多余的 goto」可在后续优化删除,但回填阶段仍按规则生成。
ML 实现 Fibonacci,main→fib0→fib1→fib2 三层嵌套定义,从 fib0(4) 调用直到 fib0(1) 即将返回,画活动记录栈(控制链 + 访问链)。
解题套路
定义 very-busy expression(从 $p$ 出发所有路径都会在被杀死前求值 $e$),给出其数据流算法;对具体 CFG 求繁忙集;判定 earliest expression,并给出 earliest[p] 关于 avail/busy/live 的公式。
解题套路
earliest 大致 $=$「繁忙但尚不可用」$\approx \text{busy}[p]\cap\neg\text{avail}[p]$,是部分冗余消除(PRE)的放置点。
这是最接近研究生级 Lazy Code Motion 的真题,非常适合作为实验班加难母题。
第 15 章 · 期末模拟练习(实验班难度)自编加难
文法 $S\to aAd\mid bBd\mid aBe\mid bAe$;$A\to c$;$B\to c$。判断它是 LR(0) / SLR(1) / LALR(1) / LR(1) 中的哪几级,并说明 LALR 合并时发生了什么。
加难点:要求构造出现 R/R 冲突的具体状态,并解释 LR(1) 为何无冲突。
分析
读到 $ac$:可能来自 $S\to aAd$($[A\to c\bullet,d]$)或 $S\to aBe$($[B\to c\bullet,e]$)。读到 $bc$:来自 $S\to bBd$($[B\to c\bullet,d]$)或 $S\to bAe$($[A\to c\bullet,e]$)。
LR(1):$ac$ 状态含 $[A\to c\bullet,d]$、$[B\to c\bullet,e]$,展望符不同($d$ 归约 $A$、$e$ 归约 $B$)→ 无冲突。$bc$ 状态同理。
LALR(1):$ac$ 与 $bc$ 是同心项集(核心都是 $\{A\to c\bullet, B\to c\bullet\}$),合并后展望符并集:$A\to c\bullet$ 带 $\{d,e\}$、$B\to c\bullet$ 带 $\{d,e\}$ → 对 $d$、$e$ 都既能归约 $A$ 又能归约 $B$ → 归约/归约冲突。
结论:非 SLR、非 LALR,仅 LR(1)。这正是「LALR 合并同心集可能新增 R/R 冲突」的标准反例。
定义「僵尸语句」:使用的变量「总是未初始化」的语句。设计数据流分析(域 / 方向 / 传递函数 / 交汇),并讨论级联消除。
加难点:① 区分 may-uninitialized(取并)与 must-uninitialized(取交)两套格并说明保守方向;② 把级联消除形式化为 worklist 不动点迭代并给复杂度。
第一步:未初始化传播(前向)
「必然未初始化」用 must(取交):域 = 变量集,方向前向,ENTRY 处所有局部变量未初始化。语句 x = expr:若 expr 任一变量 $\in$ 未初始化集 → 该语句是僵尸;否则把 $x$ 移出未初始化集。汇合 $\text{IN}[B]=\bigcap_{P}\text{OUT}[P]$。
第二步:级联消除(worklist)
删除一条僵尸语句后,原本依赖其结果的语句可能也变僵尸 → 用 worklist:初始化所有僵尸语句入队;出队删除后,把使用其定值的语句重新检查入队。
复杂度:每条语句最多被重新检查 $O(\text{依赖它的语句数})$ 次,整体 $O(|V|\cdot|\text{vars}|)$。终止性由「未初始化集单调收缩 + 僵尸集单调增长」保证。
给定含 if-else 汇合的 CFG,① 转为块参数形式 SSA(Koopa 风格);② 用两趟法消去冗余块参数;③ 再转回非 SSA 的三地址码(需插入复制指令)。
① 转 SSA
每个基本块对块内重复赋值的变量重命名(local value numbering);汇合块把来自不同前驱、有不同定值的变量列为块参数,前驱跳转处传实参 goto join(x_then, ...) / goto join(x_else, ...)。
② 两趟优化
第二趟检查每个块参数:若所有跳转到该块的实参都是同一个名字(或同一来源),则移除该参数并用该名字替换所有使用。
③ 转回非 SSA
把块参数 / φ 函数下沉为复制指令:在每个前驱块末尾、跳转前插入 join_param = 该前驱传入的实参,再普通跳转。注意并行复制的交换问题(如 swap)须引入临时变量。
① 对 let f x = (let g y = x + y in g) in (f 2) 3 写出各层类型上下文 $\Gamma$ 并推导整体类型;② 解释类型可靠性证明中,为什么 let 情形必须强化命题(引入 $\rho:\Gamma$)。
① 类型推导
$f : \text{int}\to(\text{int}\to\text{int})$。各层上下文:
$\Gamma_1 = \Gamma_4;\,x:\text{int}$
$\Gamma_3 = \Gamma_1;\,g:\text{int}\to\text{int}$
$\Gamma_2 = \Gamma_3;\,y:\text{int}$
$g=\lambda y.\,x+y$ 类型 $\text{int}\to\text{int}$;$f$ 返回 $g$,故 $f:\text{int}\to(\text{int}\to\text{int})$。$(f\,2)$ 得 $\text{int}\to\text{int}$,再应用 $3$ → 整体类型 $\text{int}$。
② 为何强化命题
原命题「$\varnothing\vdash E:T \wedge []\vdash E\Rightarrow v \Rightarrow v:T$」在 let 情形归纳会卡住:$E_2$ 在扩展环境 $[x\mapsto v_1]\rho$ 下求值、在扩展上下文 $\Gamma;x:T_1$ 下定型,无法套用「空环境/空上下文」的归纳假设。
必须强化为「$\Gamma\vdash E:T \wedge \rho\vdash E\Rightarrow v \wedge \rho:\Gamma \Rightarrow v:T$」:由归纳假设 $v_1:T_1$ → $[x\mapsto v_1]\rho : \Gamma;x:T_1$(环境一致性扩展)→ 再用归纳假设得 $v_2:T_2$。变量、letfun、call 情形同样依赖 $\rho:\Gamma$。
在 2016 ML 嵌套函数题基础上,让内层函数 g 作为返回值逃逸出定义它的 f。讨论:① 为什么栈式活动记录管理失效;② 闭包如何捕获自由变量 x;③ 给出闭包的运行时表示。
① 栈式失效:$f$ 返回后其活动记录出栈,但返回的 $g$ 仍需访问 $f$ 的局部变量 $x$ → 若 $x$ 在已出栈的活动记录里就是悬空引用。
② 捕获自由变量:对 $g$ 做闭包转换,分析出其自由变量 $\{x\}$,把 $x$ 的值复制/移到堆上的闭包环境中(Lua 的 upvalue 逃逸即此)。
③ 运行时表示:$g$ 的闭包 = { func: &g_code, env: { x: 7 } }。调用 $g(a)$ 时通过 closure.func(closure.env, a),在 env 中取 $x$。活动记录改为堆分配或仅把逃逸变量提升到堆。
第 16 章 · 互动自测题(带打分)自编练习
共 30 道选择/多选题,覆盖 Lect08-15 进阶考点。题目基于课件知识点整理。
📝 期末知识点自测
第 17 章 · 期末考前速查表 整理速查
17.1 必背公式 / 结论
LR 能力: $\text{LR}(0)\subsetneq\text{SLR}(1)\subsetneq\text{LALR}(1)\subsetneq\text{LR}(1)$;$\text{LL}(k)\subsetneq\text{LR}(k)$;LALR 状态数 = LR(0)。
LR(1) 闭包: $[A\to\alpha\bullet B\beta,a]\Rightarrow[B\to\bullet\gamma,b],\;b\in\text{First}(\beta a)$。
活跃变量: $\text{IN}=\text{use}\cup(\text{OUT}-\text{def})$,后向,$\cup$。
访问链: 建立走 $m-n+1$ 步,访问走 $m-n$ 步。
数组: $\text{addr}=base+(\cdots(i_1 n_2+i_2)\cdots+i_k)\times w$。
17.2 易错陷阱(必看)
区别只在归约用不用 Follow。
不会新增 S/R。
移进由 GOTO 唯一决定。
需弱引用或标记-清扫。
活跃/可达取并(may)。
可达/可用=前向。
需 LL 底层 + 标记符号。
静态作用域,不是调用时。
可多项式最优着色。
引入 $\rho:\Gamma$ 才能归纳。
可能指数膨胀。
闭包转换优化。
17.3 答题策略 经验建议
- 必答题优先抓满分:LR 分析表、SDT 回填、运行时(访问链/GC)、活跃变量+图染色——这些是机械工作,按部就班。
- 选答题择优:函数式语言与类型系统「作业做过」,性价比高;SSA 生成需多练块参数转换。
- 数据流题先写清「方向 + 交汇 + 传递函数 + 边界」四要素,再迭代。
- 类型推导题严格按规则写各层 $\Gamma$,不要望文生义。
编译原理期中
一站式复习
覆盖 Lect01-07 全部考点,结合 2016/2017/2018 三年真题与自编练习,按"课件知识点 → 真题套路 → 自测巩固"递进。
📊 考点权重(基于三年真题统计)非课件内容
LR 分析
每年必考 20-25 分大题:LR(0)/SLR/LALR/LR(1) 项集族 + 分析表 + 走栈
LL(1) 分析
每年必考 15 分大题:消除左递归/左公因子 + First/Follow + 预测表
RE & 自动机
15 分大题:RE→NFA→DFA→最小 DFA 完整流程
选择 & 简答
概念辨析、文法判别、计数题、语言描述
🗺️ 考点路线图 整理导图
词法分析(Lect02)
RE ⇄ NFA ⇄ DFA,DFA 最小化 · 每年必考大题
CFG 基础(Lect03)
推导 / 语法树 / 二义性 / 句柄 / 规范句型
LL(1) 分析(Lect03)
消除左递归 → First/Follow → 预测表 · 每年必考
LR 系列(Lect03)
LR(0) → SLR(1) → LALR(1) → LR(1) · 每年必考大题
语义分析(Lect04)
S/L-属性 SDD、SDT、依赖图、类型检查、符号表
中间表示(Lect05)
三地址码、四元式 / 三元式 / 间接三元式、回填、数组寻址
运行时环境(Lect06)
栈帧、静态 / 动态作用域、参数传递、堆管理 / GC
代码生成(Lect07)
基本块、活跃信息、寄存器分配、指令选择、窥孔优化
第 1 章 · 编译器概述
核心阶段
源码 → 词法分析(Token 流) → 语法分析(AST) → 语义分析(带类型 AST) → 中间代码(IR) → 优化 → 目标代码。
易错概念
- 编译器 vs 解释器:解释器逐句执行,不一定生成机器码,但也可能生成中间表示。"解释器不生成可执行机器码"作为唯一区别不严格。
- 前端(依赖语言):词法、语法、语义;后端(依赖机器):代码生成、机器相关优化。
A 错:NFA 与 DFA 表达能力相同(可互转)。
B 错:$S \to aS \mid bS \mid ab$ 必须以 ab 结尾,并非任意串。
C 错:LR 系列分析不需要消除左递归。
D 对:CFG 严格包含正则语言。
第 2 章 · 正则表达式
2.1 三种基本运算
| 运算 | 含义 | 例子 |
|---|---|---|
选择 r|s | r 或 s | a|b = {a, b} |
连接 rs | r 后接 s | ab = {ab} |
闭包 r* | r 重复 0 或多次 | a* = $\{\varepsilon, a, aa, \dots\}$ |
扩展运算:r+(≥1 次)、r?(0 或 1 次)。
2.2 计数题套路
(... | ε),乘法原理不去重会重复计数。常用做法:① 直接乘法:把每段的"非空选项数+1(含 ε)"相乘 → 得到全部串(含空串、重复)。
② 按长度分类计数:枚举每段是否选中,再乘起来;最后扣掉空串。
(a|b|c|ε)(a|b|c|ε)(a|b|c|ε)(x|y|ε) 不同非空串数?前 3 段每段 4 选 1(含 ε),但要按"实际生成串的长度"算,避免重复。
设前 3 段产生的串集合 S:长度可为 0~3。
长度 0:1 种(ε);长度 1:C(3,1)·3 = 9;长度 2:C(3,2)·9 = 27;长度 3:27 → 共 64 种串。
第 4 段 3 选 1 → 总 64 × 3 = 192 串(含重复)。
实际不同串:
前 3 段生成: ε(1)+ 长度1: 3 + 长度2: 9 + 长度3: 27 = 40 种
第 4 段:x、y、ε → 共 40 × 3 = 120,扣掉空串 → 119。
(a|b|c|d|ε)(a|b|c|ε)(a|b|ε)(a|b) 不同串数?前 3 段可选字符与第 4 段有重叠(a、b 出现在多段),需用集合去重。
seg1={ε,a,b,c,d}(5),seg2={ε,a,b,c}(4),seg3={ε,a,b}(3),seg4={a,b}(2,无 ε)。
枚举所有拼接并去重,共 82 种不同串(可用程序验证)。
2.3 描述特定语言的正则表达式
例:字母表 {0,1} 上不含 001 的串:
- 分析:一旦看到
00,下一个字符不能是1。 - 等价 RE:
(1|01)*0*
b*(ab*ab*)* 表示什么语言?分析:外层
(ab*ab*)* 每次产生恰好 2 个 a;前后及中间任意数量 b。
第 3 章 · 有限自动机
3.1 五元组定义
$M = (Q, \Sigma, \delta, q_0, F)$:状态集、字母表、转移函数、初态、终态集。
- DFA:$\delta : Q \times \Sigma \to Q$(确定)。
- NFA:$\delta : Q \times (\Sigma \cup \{\varepsilon\}) \to 2^Q$(不确定 + ε 转移)。
- 表达能力相同:DFA 与 NFA 等价(可互转)。
3.2 DFA 模拟
读串过程:从 $q_0$ 出发,每读一个字符按 $\delta$ 转移,串读完时若处于 $F$ 则接受。
第 4 章 · RE → NFA → DFA → 最小化
4.1 RE → NFA(Thompson 构造法)
规则(递归):
| RE | NFA 形态 |
|---|---|
| $\varepsilon$ | 一个 $\varepsilon$ 边相连的两状态 |
| $a$ | 一条标 $a$ 的边 |
| $r \mid s$ | 新增起点/终点,$\varepsilon$ 跳到 $r$、$s$ 的入口;$r$、$s$ 出口 $\varepsilon$ 到新终点 |
| $rs$ | $r$ 的出口 $\varepsilon$ 接 $s$ 的入口 |
| $r^*$ | 新增起点终点,$\varepsilon$ 跳过 $r$;$r$ 出口 $\varepsilon$ 回到 $r$ 入口;起点 $\varepsilon$ 直达终点 |
4.2 NFA → DFA(子集构造法)
- 计算 $\varepsilon$-closure($s$):从 $s$ 出发只走 $\varepsilon$ 边能到达的所有状态。
- 初态 = $\varepsilon$-closure($q_0$)。
- 对当前 DFA 状态 $T$ 和每个输入 $a$,计算 $\text{move}(T, a)$,再取 $\varepsilon$-closure,得到新 DFA 状态。
- 重复直到无新状态。任何含原 NFA 终态的 DFA 状态都是终态。
4.3 DFA 最小化(等价类划分 / Hopcroft)
- 初始划分:$\{F,\; Q \setminus F\}$(终态集、非终态集)。
- 对每个组 $G$,对每个输入 $a$:若 $G$ 中状态在 $a$ 上转到不同分组 → 把 $G$ 拆分。
- 重复直到稳定。每个等价类合并为一个状态。
1(0|1)*(11)*01 完成 NFA→DFA→最小 DFA。① Thompson 法严格构造 NFA(每个子表达式两个端点)。
② 子集构造:标注每个 DFA 状态是哪几个 NFA 状态的集合。
③ 最小化:先分终态/非终态两组,按转移分裂。
本题最终最小 DFA 一般为 3-4 个状态。
(1(0*1*)*1+)|0 的最小 DFA?(0*1*)* = (0|1)*,所以左半部 = "1 开头、1 结尾的至少含两个 1 的 0/1 串"。加上
|0,最终最小 DFA 通常 4 个状态:起点、读 0 终止、读 1 中、读 1 中且可终。
第 5 章 · 上下文无关文法
5.1 基本概念
- 四元组 $G = (V_T, V_N, P, S)$:终结符、非终结符、产生式、开始符。
- 句型:从 S 推导出的串(含非终结符);句子:仅含终结符的句型。
- 语言 L(G) = 文法产生的全部句子的集合。
- 最左推导:每步替换最左非终结符;最右推导(规范推导):每步替换最右。
5.2 二义性
$\Leftrightarrow$ 有两个不同最左推导 $\Leftrightarrow$ 有两个不同最右推导。
⚠️ 注意:"存在多个推导"≠ 二义(最左和最右本身就是不同推导,但树相同)。
5.3 句柄(Handle)
对规范句型 $\gamma$,最右推导 $S \Rightarrow^* \alpha A w \Rightarrow \alpha\beta w$ 中,$\beta$ 即为句柄(最左可归约串及其位置)。
判断技巧:在当前句型对应的语法树中,找最左边的、最底层的完整产生式子树,其产生式右部即为句柄。
Sa+a* 的句柄?最右推导:$S \Rightarrow SS{*} \Rightarrow SSa{*} \Rightarrow S(SS{+})a{*} \Rightarrow SSa{+}a{*}$ $\Rightarrow$ Sa+a* $\Rightarrow$ Saa+a*…
实际倒推:Sa+a* ← S(a)+a*,把 $a$ 归约为 $S$ 即得到上一步的 SS+a*;故句柄就是 a。
要求:句型可由某次规范推导的最后一步用 $S \to SS\&$ 得到,且 $SS\&$ 在最右非终结符位置。
逐项检查 $SS\&$ 是否处于最右可归约位置即可。
5.4 文法等价改写
消除左递归:$A \to A\alpha \mid \beta \;\Longrightarrow\; A \to \beta A',\; A' \to \alpha A' \mid \varepsilon$
提取左公因子:$A \to \alpha\beta \mid \alpha\gamma \;\Longrightarrow\; A \to \alpha A',\; A' \to \beta \mid \gamma$
第 6 章 · LL(1) 自顶向下分析
6.1 First 集合
- 若 $X$ 是终结符:$\text{First}(X) = \{X\}$。
- 若 $X \to \varepsilon$:把 $\varepsilon$ 加入 $\text{First}(X)$。
- 若 $X \to Y_1 Y_2 \dots Y_k$:把 $\text{First}(Y_1)\setminus\{\varepsilon\}$ 加入;若 $Y_1 \Rightarrow^* \varepsilon$,再加 $\text{First}(Y_2)\setminus\{\varepsilon\}$…;若全部可推 $\varepsilon$,则把 $\varepsilon$ 加入。
6.2 Follow 集合
- $\$ \in \text{Follow}(S)$($S$ 是开始符)。
- 若 $A \to \alpha B \beta$:$\text{Follow}(B) \supseteq \text{First}(\beta)\setminus\{\varepsilon\}$。
- 若 $A \to \alpha B$,或 $A \to \alpha B \beta$ 且 $\beta \Rightarrow^* \varepsilon$:$\text{Follow}(B) \supseteq \text{Follow}(A)$。
6.3 预测分析表
对每个产生式 $A \to \alpha$:
- 对每个 $a \in \text{First}(\alpha)\setminus\{\varepsilon\}$:$M[A,a] = A \to \alpha$
- 若 $\varepsilon \in \text{First}(\alpha)$:对每个 $b \in \text{Follow}(A)$:$M[A,b] = A \to \alpha$
LL(1) 判定:每格至多一条产生式(即对同一 $A$,不同产生式的 First 互不相交;若有 $\varepsilon$,First 与 Follow 也不相交)。
6.4 完整例题(仿 2018-四)
原文法:$S \to (S) \mid S{*}A \mid A$;$A \to A{+}B \mid B$;$B \to \text{digit}\,B \mid \text{digit} \mid \varepsilon$。
$S$ 的 $S \to S{*}A$ 是左递归,按标准模板($\alpha={*}A,\;\beta=(S)\mid A$)改写:
$S \to (S)\,S' \mid A\,S'$
$S' \to {*}A\,S' \mid \varepsilon$
$A \to A{+}B$ 同理消左递归:
$A \to B\,A'$
$A' \to {+}B\,A' \mid \varepsilon$
$B$ 的前两条产生式 $\text{digit}\,B$ 与 $\text{digit}$ 含左公因子 digit,且因为 $B \Rightarrow \varepsilon$,
digit 这条与 digit B 是冗余的。等价化简为:$B \to \text{digit}\,B \mid \varepsilon$
最终 $G'[S]$:
$S \to (S)\,S' \mid A\,S'$
$S' \to {*}A\,S' \mid \varepsilon$
$A \to B\,A'$
$A' \to {+}B\,A' \mid \varepsilon$
$B \to \text{digit}\,B \mid \varepsilon$
② FIRST / FOLLOW:
| 非终结符 | FIRST | FOLLOW |
|---|---|---|
| $S$ | $\{(,\;\text{digit},\;+,\;*,\;\varepsilon\}$ | $\{\$,\;)\}$ |
| $S'$ | $\{*,\;\varepsilon\}$ | $\{\$,\;)\}$ |
| $A$ | $\{\text{digit},\;+,\;\varepsilon\}$ | $\{*,\;\$,\;)\}$ |
| $A'$ | $\{+,\;\varepsilon\}$ | $\{*,\;\$,\;)\}$ |
| $B$ | $\{\text{digit},\;\varepsilon\}$ | $\{+,\;*,\;\$,\;)\}$ |
③ LL(1) 预测分析表:
| digit | * | + | ( | ) | $ | |
|---|---|---|---|---|---|---|
| $S$ | $AS'$ | $AS'$ | $AS'$ | $(S)S'$ | $AS'$ | $AS'$ |
| $S'$ | ${*}AS'$ | $\varepsilon$ | $\varepsilon$ | |||
| $A$ | $BA'$ | $BA'$ | $BA'$ | $BA'$ | $BA'$ | |
| $A'$ | $\varepsilon$ | ${+}BA'$ | $\varepsilon$ | $\varepsilon$ | ||
| $B$ | $\text{digit}\,B$ | $\varepsilon$ | $\varepsilon$ | $\varepsilon$ | $\varepsilon$ |
④ 是否 LL(1)? 是。
判定:对每个有多个候选式的非终结符,验证候选式之间预测集(PREDICT)两两不相交。
$S$:$\text{PREDICT}(S\to(S)S')=\{(\}$,$\text{PREDICT}(S\to AS')=\{\text{digit},+,*\}\cup\text{FOLLOW}(S)=\{\text{digit},+,*,\$,)\}$,不相交 ✓
$S'$:$\{*\}$ 与 $\{\$,)\}$ 不相交 ✓
$A'$:$\{+\}$ 与 $\{*,\$,)\}$ 不相交 ✓
$B$:$\{\text{digit}\}$ 与 $\{+,*,\$,)\}$ 不相交 ✓
所有非终结符均无冲突,$G'[S]$ 是 LL(1) 文法。
A、D 含左递归 ✗;B 两条产生式 First 都含 a,冲突 ✗;C 两条 First={a}、{b} 不冲突 ✓。
第 7 章 · LR(0) 与 SLR(1) 分析
7.1 LR 项目(Item)
LR(0) 项目:$A \to \alpha \cdot \beta$(产生式右部加一个圆点)。
- 圆点在末尾 → 归约项($A \to \alpha\cdot$)。
- 圆点在终结符前 → 移进项。
- $A \to \cdot\alpha$ 表示正等待识别 $A$。
7.2 闭包 $\text{Closure}(I)$
- $I$ 中每项都加入 $\text{Closure}(I)$。
- 若 $A \to \alpha\cdot B\beta \in \text{Closure}(I)$,则把 $B$ 的所有产生式 $B \to \cdot\gamma$ 加入。
- 重复至稳定。
7.3 $\text{GOTO}(I, X)$
$\text{GOTO}(I, X) = \text{Closure}(\{A \to \alpha X\cdot\beta \mid A \to \alpha\cdot X\beta \in I\})$。
7.4 LR(0) DFA & 分析表
- 拓广文法:$S' \to S$。
- 初始状态 $I_0 = \text{Closure}(\{S' \to \cdot S\})$。
- 不断 GOTO 直至闭合。
- 对状态 $I_i$:
- 若 $\text{GOTO}(I_i, a) = I_j$($a$ 终结符)→ $\text{ACTION}[i, a] = s_j$(移进)。
- 若 $A \to \alpha\cdot \in I_i$($A \neq S'$)→ SLR:对所有 $a \in \text{Follow}(A)$:$\text{ACTION}[i, a] = r_k$($k$ 是产生式编号)。
- 若 $S' \to S\cdot \in I_i$ → $\text{ACTION}[i, \$] = \text{acc}$。
- $\text{GOTO}(I_i, A) = I_j$($A$ 非终结符)→ $\text{GOTO}[i, A] = j$。
7.5 冲突
• 移进/归约(S/R):状态中既有 $A \to \alpha\cdot$ 又有 $B \to \beta\cdot a\gamma$,且 $a$ 终结符。
• 归约/归约(R/R):状态中两个归约项 $A \to \alpha\cdot$、$B \to \beta\cdot$,对同一展望符冲突。
不可能出现"移进/移进"冲突(移进只看 GOTO,是确定的)。
7.6 SLR(1) vs LR(0)
- 项集族完全相同(这是常见错点!)。
- 区别仅在归约:LR(0) 对归约项的所有终结符都填归约;SLR(1) 只对 $\text{Follow}(A)$ 中的填。
- $\text{SLR}(1) \supsetneq \text{LR}(0)$:能解决一部分 LR(0) 中的 S/R 冲突。
第 8 章 · LR(1) 与 LALR(1)
8.1 LR(1) 项目
形如 $[A \to \alpha\cdot\beta, a]$,其中 $a$ 是展望符(lookahead)。
8.2 LR(1) 闭包
若 $[A \to \alpha\cdot B\beta, a] \in I$,对 $B \to \gamma$ 的每条产生式:
把 $[B \to \cdot\gamma, b]$ 加入,其中 $b \in \text{First}(\beta a)$。
8.3 LR(1) 归约
仅当展望符为 $a$ 时,归约 $[A \to \alpha\cdot, a]$,即 $\text{ACTION}[i, a] = r_k$。
8.4 LALR(1)
- 同心项集:去掉展望符后相同的两个 LR(1) 项集。
- 合并同心项集:核相同,展望符取并集。
- 状态数 = LR(0) 状态数,但表达能力优于 SLR。
- 合并可能产生新的归约/归约冲突,但不会产生新的移进/归约冲突。
8.5 能力关系
$\text{LL}(k) \subsetneq \text{LR}(k)$(LR(k) 比 LL(k) 强)
$\text{LL}(k)$、$\text{LR}(k)$ 均 $\subsetneq$ 无二义 CFG。
关键:读到 $d$ 后,需根据后续是 $a$ 还是 $c$ 决定归约 $d \to A$ 还是 $d \to B$;展望符不同 → LR(1) 区分得开。
但合并同心项集后,会出现 $[A \to d\cdot, a/c]$、$[B \to d\cdot, a/c]$ 同心 → LALR 归约/归约冲突。
SLR 用 $\text{Follow}(A)=\{a,c\}$、$\text{Follow}(B)=\{a,c\}$,更冲突。
LL(1):$S$ 推出 $b$ 开头时 First 重叠,冲突。
8.6 LR 大题完整模板
- 拓广文法 $S' \to S$。
- 构造 LR(0) 项集族(注意每个 GOTO 都要画)。
- 构造 LR(1) 项集族(记得 $\text{First}(\beta a)$)。
- 填 ACTION/GOTO 表。
- 判定 SLR/LALR/LR(1)。
- 走栈分析输入串:(状态栈 | 符号栈 | 剩余输入 | 动作)。
第 9 章 · 语义分析(Lect04)
9.1 语法制导定义 SDD
- 综合属性(Synthesized):父结点从子结点计算 → 自下而上。典型:表达式求值 $E.val$。
- 继承属性(Inherited):从父或左兄弟计算 → 自上而下。典型:类型声明中 $T.type$ 传给标识符列表。
9.2 S-属性 vs L-属性
| 类别 | 属性限制 | 求值方式 | 适配分析器 |
|---|---|---|---|
| S-属性 SDD | 仅综合属性 | 自底向上(后序遍历) | LR 分析器(归约时求值) |
| L-属性 SDD | 继承属性只依赖父或左侧兄弟 | 深度优先(左→右) | LL 分析器 / 递归下降 |
9.3 语法制导翻译方案 SDT
- SDT 在产生式体中嵌入语义动作(花括号代码片段)。
- S-属性 SDT:所有动作放在产生式最右端。
- L-属性 SDT:继承属性的动作放在对应符号之前,综合属性放在最右端。
- 消除左递归时,需将语义动作一并变换(引入新的继承属性传递中间结果)。
9.4 依赖图与求值顺序
- 依赖图:节点 = 属性实例,边 = 依赖关系。
- 求值顺序 = 依赖图的拓扑排序。若有环则 SDD 无法求值。
- S-属性的依赖图一定无环(子→父方向)。
9.5 类型检查
- 类型表达式:基本类型、数组 $\text{array}(n, T)$、函数 $T_1 \to T_2$、记录/结构体。
- 类型等价:结构等价(递归比较结构)vs 名等价(同名才等价)。
- 类型转换:隐式(widening,如 int→float)vs 显式(cast)。
- 重载:同名函数不同参数类型 → 需根据上下文消歧。
9.6 符号表
- 每个作用域一张符号表,嵌套作用域形成链式结构。
- 查找:从当前作用域向外逐层查找(最近嵌套规则)。
- 存储信息:名字、类型、偏移量、作用域层级。
L-属性 SDD 要求继承属性只能依赖父节点或左侧兄弟的属性,不能依赖右兄弟。这保证了从左到右一趟扫描即可求值。
第 10 章 · 中间表示(Lect05)
10.1 为什么需要 IR?
- 解耦前后端:m 个语言 + n 个机器,从 m×n 降为 m+n。
- 便于机器无关优化。
- 层次化:高级 IR(保留语言结构)→ 中级 IR(三地址码)→ 低级 IR(接近汇编)。
10.2 三地址码
| 形式 | 含义 |
|---|---|
x = y op z | 二元运算 |
x = op y | 一元运算(如 x = -y) |
x = y | 赋值 |
goto L | 无条件跳转 |
if x relop y goto L | 条件跳转 |
x = y[i] / x[i] = y | 数组取/存 |
x = &y / x = *y / *x = y | 地址 / 间接寻址 |
param x; call f, n; y = call f, n | 函数调用(n 个参数) |
return y | 函数返回 |
10.3 三种存储形式
| 形式 | 结构 | 优缺点 |
|---|---|---|
| 四元式 | (op, arg1, arg2, result) | 简单直观;指令重排不影响 |
| 三元式 | (op, arg1, arg2),结果用下标引用 | 省空间;重排需更新所有引用 |
| 间接三元式 | 三元式表 + 执行顺序表 | 重排只改顺序表,便于优化 |
10.4 表达式翻译
对 $E \to E_1 + E_2$,使用 newtemp() 生成临时变量,gen() 生成代码:
E.place = newtemp(); gen(E.place, '=', E1.place, '+', E2.place); E.code = E1.code || E2.code || (上面这条);
10.5 控制流的回填(Backpatching)
- 布尔表达式短路求值:用 truelist / falselist 记录待回填的跳转。
B → B1 || M B2:M.quad 是 B2 代码起点;回填 B1.falselist → M.quad;B.truelist = B1.truelist $\cup$ B2.truelist;B.falselist = B2.falselist。B → B1 && M B2:回填 B1.truelist → M.quad。S → if B then M S1:回填 B.truelist → M.quad;S.nextlist = B.falselist $\cup$ S1.nextlist。
10.6 数组寻址
对 $a[i_1, i_2, \dots, i_k]$(行主序):
$\text{addr} = \text{base} + (((i_1 \cdot d_2 + i_2) \cdot d_3 + i_3) \cdots) \cdot w$
其中 $d_j$ 是第 j 维大小,$w$ 是元素宽度。
间接三元式将"指令本身"和"执行顺序"分离,重排只需修改顺序表,三元式表不动,所有引用仍然有效。三元式重排会破坏下标引用;四元式重排没问题但临时变量名要变。
第 11 章 · 运行时环境(Lect06)
11.1 存储分区
- 代码区(只读):编译期确定大小。
- 静态区:全局/静态变量,编译期确定。
- 栈区:函数活动记录(栈帧),动态生长。
- 堆区:动态分配(malloc/new),程序员或 GC 管理。
11.2 栈帧(Activation Record)
- FP(帧指针):指向当前栈帧的固定位置,访问局部变量靠 fp+offset。
- SP(栈顶指针):栈顶,分配新栈帧用。
- 控制链:指向调用者栈帧,返回时恢复。
- 访问链(静态链):每个活动记录中保存指向静态外围作用域栈帧的链指针,用于逐层访问非局部变量。
- display:按词法层次保存活动记录入口的表,可直接定位外层活动记录。
11.3 静态作用域 vs 动态作用域
| 静态(词法)作用域 | 动态作用域 | |
|---|---|---|
| 查找规则 | 按源码嵌套结构(最近词法外层) | 按调用链(最近调用者) |
| 实现机制 | 访问链或 display | 控制链 / 深度绑定 / 浅绑定 |
| 典型语言 | C, Java, Python(变量) | 早期 LISP, Bash 局部变量 |
11.4 参数传递
| 方式 | 说明 | 语言例子 |
|---|---|---|
| 值传递 (call by value) | 复制实参值,被调修改不影响实参 | C, Java(基本类型) |
| 引用传递 (call by reference) | 传地址,被调修改即修改实参 | C++ &, Pascal var |
| 值结果 (call by value-result / copy-restore) | 入口复制值,出口写回 | Ada in-out |
| 名传递 (call by name) | 实参按表达式替换并在使用点重新求值,不同于现代的 call by need | ALGOL 60 |
11.5 堆管理
- 显式管理:malloc/free(C),new/delete(C++)→ 易内存泄漏 / 悬空指针。
- 引用计数:每对象计数,归零回收。无法处理循环引用。
- 标记-清除:从根(栈、全局)DFS 标记可达,清除未标记。会产生碎片。
- 复制收集:堆分两半,每次只用一半,存活对象复制到另一半 → 无碎片,但浪费一半空间。
- 分代收集:基于"大多数对象短命"假设,年轻代频繁回收,老年代少回收。
循环引用对象互相引用,计数永不为 0,导致内存泄漏。A 是标记-清除的特点;C 是标记-清除可能产生;D 是复制收集的代价。
第 12 章 · 代码生成(Lect07)
12.1 代码生成器的输入
- 中间表示(三地址码 / IR)
- 符号表信息(变量地址 / 类型)
- 目标机模型(指令集、寻址方式、寄存器数量)
12.2 基本块与流图
- 基本块:单入口、单出口的最大语句序列。入口是首条 / 跳转目标 / 跳转后第一条;出口是跳转 / return / 块末尾。
- 流图(CFG):节点 = 基本块,边 = 控制流(跳转 / 顺序)。
- 用于数据流分析、循环识别、优化。
12.3 下一次使用 / 活跃信息
- 活跃(live):变量在某点之后会被使用且未被重新赋值。
- 下次使用:在该基本块内,变量下次被使用的语句位置。
- 计算方法:从基本块末尾倒推(块尾所有非临时变量初始为活跃):
- 对语句
x = y op z: - ① 把 x、y、z 的当前活跃/下次使用信息附加到该语句。
- ② 标 x 为不活跃、无下次使用。
- ③ 标 y、z 为活跃、下次使用 = 当前语句。
- 对语句
12.4 寄存器分配(简单算法)
用 getReg(I) 为指令 I 选择目标寄存器:
- 若操作数已在寄存器 R 中且之后不再使用,复用 R。
- 否则取空闲寄存器。
- 都满则溢出:选最远未来才用的寄存器,把它的值存回内存。
12.5 指令选择
- 树覆盖(Tree Tiling):把 IR 树用一组指令模板(tile)覆盖,每个 tile 对应一条机器指令。
- Maximal Munch:贪心,每次匹配最大的 tile。
- 动态规划:在所有覆盖中选总代价最小者。
12.6 窥孔优化(Peephole)
- 冗余 load/store 删除:
store x; load x→ 第二条删去。 - 无用代码消除:跳转到下一条立即位置。
- 控制流简化:
goto L1; L1: goto L2→goto L2。 - 代数化简:
x = x + 0删除;x = x * 2→x = x << 1(强度削减)。 - 机器特化:用专门指令(如自增 inc x)替换通用序列。
12.7 DAG 表示与基本块优化
- 把基本块构造成 DAG:相同子表达式合并为同一节点。
- 识别公共子表达式。
- 识别无用赋值(其结果未被块外使用)。
- 从 DAG 重新生成代码 → 减少指令数。
按 Dragon Book 标准定义,基本块的出口是跳转指令(goto / 条件跳转 / return)。函数调用(call)在严格定义中不一定切分基本块——被调函数正常返回后控制流仍顺序执行。但部分教材将 call 视为潜在跳转而切分。
考试以课件定义为准:通常只有跳转/return/标号才切分基本块,故 B 错。D 正确(基本块内只有顺序执行,条件跳转只出现在末尾)。
第 13 章 · 真题选择题精选
A、B、C 是教材中最常见也最稳妥的二义性等价表述:某句子存在两棵不同语法树、两个不同最左推导或两个不同最右推导。
D 与规范归约过程关系更近,但通常不直接作为课件里的标准等价定义来给出,这里不列入标准答案。
A 错:SLR 与 LR(0) 项集族完全相同,区别仅在归约时是否用 Follow 集。
B 错:LR 系列不需要消除左递归(这是 LL 的需求)。
C 错:句柄属于最右推导(规范推导),不是最左推导。
D 对:LALR(1) 能力强于 SLR(1),但状态数与 LR(0)/SLR 相同。
某句子存在多棵语法树、多个最左推导、多个最右推导,本质上都在说明文法对同一句子给出了不唯一的句法结构。
这三条是课件里最标准的等价条件;D 不作为这里的标准答案。
移进 5 个终结符(a,+,a,*,a),归约 5 次。一个合法的归约思路可写成:先把第一个 $a$ 归约为 $T$ 再归约为 $E$;随后把后半段 $a*a$ 归约为 $T$;最后把 $E+T$ 归约为 $E$。
这里不存在 $T+T\to E$ 这种产生式;应始终按题目给定文法 $E\to E+T\mid T$、$T\to a*T\mid a$ 计数。
核心特征:$C$ 只能生成 $x$、$y$ 数量相等的平衡串;$A$、$B$ 则分别在此基础上生成以 $x$ 或 $y$ 倾斜的串。
A 可推;D 也可由 $B$ 分支递归推出。
B =
yyyxxx 虽然 $\#x=\#y$,但开始符号并不能直接进入纯平衡的 $C$ 分支,所以它不可推。C =
yxxy 同样不满足该文法从 $A$ 或 $B$ 出发的构造方式。
第 14 章 · 大题套路与模板 整理模板
14.1 RE/NFA/DFA 大题模板
① NFA:用 Thompson 法严格画,每个子表达式独立画块再连。注意 r* 必须有跳过边和回环边。
② DFA:列子集构造表(行=DFA 状态,列=输入字符),每行内容是 $\varepsilon$-closure 后的状态集。注意标记哪个 DFA 状态包含原 NFA 终态。
③ 最小化:先两组(终/非终),逐字符检查每组内是否同分组转移,否则拆分。
14.2 LL(1) 大题模板
- 消左递归:$A \to A\alpha \mid \beta \;\Rightarrow\; A \to \beta A',\; A' \to \alpha A' \mid \varepsilon$。
- 提左公因子:$A \to \alpha\beta \mid \alpha\gamma \;\Rightarrow\; A \to \alpha A',\; A' \to \beta \mid \gamma$。
- 计算 First/Follow:从底层非终结符开始迭代。
- 填表:$\varepsilon \in \text{First}(\alpha)$ 时记得用 $\text{Follow}(A)$ 填。
- 判定:是否每格 ≤ 1 条产生式。
14.3 LR 大题模板
- 拓广:$S' \to S$。
- LR(0) 项集族:从 $I_0 = \text{Closure}(\{S' \to \cdot S\})$ 出发,逐字符做 GOTO。
- LR(1) 项集族:每项加展望符,闭包用 $\text{First}(\beta a)$ 计算 $b$。
- 填表:ACTION(移进 sn / 归约 rn / 接受 acc)+ GOTO(数字)。
- 判定:检查冲突。
- 有 S/R 或 R/R → 不是 SLR/LALR/LR(1)(对应判定方法)。
- SLR 用 Follow,LALR 用合并后的展望符,LR(1) 用原展望符。
- 走栈:分析串时,状态栈和符号栈同步更新。
14.4 LR 走栈表格示例
以表达式文法为例(拓广后):
S' → E E → E + T | T T → T * F | F F → ( E ) | id
LR(0) 项集族共 12 个状态(I0–I11),产生式编号:① $E \to E+T$ ② $E \to T$ ③ $T \to T*F$ ④ $T \to F$ ⑤ $F \to (E)$ ⑥ $F \to id$
以下按 SLR(1) 分析表走栈,分析串 id*id$:
| 步骤 | 状态栈 | 符号栈 | 剩余输入 | 动作 |
|---|---|---|---|---|
| 1 | 0 | id*id$ | s5(移进 id,GOTO(0,id)=5) | |
| 2 | 0 5 | id | *id$ | r6($F \to id$),GOTO(0,F)=3 |
| 3 | 0 3 | F | *id$ | r4($T \to F$),GOTO(0,T)=2 |
| 4 | 0 2 | T | *id$ | s7(移进 *,GOTO(2,*)=7) |
| 5 | 0 2 7 | T * | id$ | s5(移进 id,GOTO(7,id)=5) |
| 6 | 0 2 7 5 | T * id | $ | r6($F \to id$),GOTO(7,F)=10 |
| 7 | 0 2 7 10 | T * F | $ | r3($T \to T*F$),pop 3,GOTO(0,T)=2 |
| 8 | 0 2 | T | $ | r2($E \to T$),GOTO(0,E)=1 |
| 9 | 0 1 | E | $ | acc ✓ |
① 移进:查 ACTION[栈顶状态, 当前输入符] → 得 "sn",将状态 n push 入栈。
② 归约:按产生式 $A \to \beta$,pop 掉 $|\beta|$ 个状态,再查 GOTO[新栈顶状态, $A$] 得到新状态入栈。
③ 接受:栈顶为最终项 $S' \to E\cdot$ 且输入只剩 $\$ $ → acc。
第 15 章 · 真题大题精解(手把手) 真题整理
三年真题中最具代表性的大题,按"题面 → 思路 → 完整解答"逐步展开。点击展开查看详细解答。
给定正则表达式 $r = (a|b)^*abb$,构造 DFA 并最小化。
第一步:RE → NFA(Thompson 构造)
第二步:NFA → DFA(子集构造)
$T_1 = \text{move}(T_0, a) \cup \varepsilon = \{1,2,3,4,6,7,8\}$(含 a 后转移)
$T_2 = \text{move}(T_0, b) = \{1,2,4,5,6,7\}$
$T_3 = \text{move}(T_1, a) = \{1,2,3,4,6,7,8\}$(同 $T_1$)
$T_3' = \text{move}(T_1, b) = \{1,2,4,5,6,7,9\}$
$T_4 = \text{move}(T_3', b) = \{1,2,4,5,6,7,10\}$ → 含 10(接受态)
得到 5 个 DFA 状态:A=T0, B=T1, C=T2, D=T3', E=T4。其中 E 为接受态。
| 状态 | a | b |
|---|---|---|
| A | B | C |
| B | B | D |
| C | B | C |
| D | B | E |
| E* | B | C |
第三步:DFA 最小化(Hopcroft 等价类划分)
检查 $\{A,B,C,D\}$ 在 a/b 下转移:
A 在 b 下到 C(同组),D 在 b 下到 E(异组) → 拆分 D 出来。
$\Pi_1 = \{ \{E\}, \{D\}, \{A,B,C\} \}$
检查 $\{A,B,C\}$:A 在 a 下到 B(同),B 在 a 下到 B(同),C 在 a 下到 B(同);A 在 b 下到 C(同),B 在 b 下到 D(异),C 在 b 下到 C(同) → 拆分 B 出来。
$\Pi_2 = \{ \{E\}, \{D\}, \{B\}, \{A,C\} \}$
再检查 {A,C}:A 在 a 到 B、C 在 a 到 B;A 在 b 到 C、C 在 b 到 C → 同组,稳定。
最小 DFA:4 个状态 {AC, B, D, E}。
易错点
- 子集构造时务必算 $\varepsilon$-closure,否则状态会少。
- 最小化时初始划分必须是接受态和非接受态两组。
- 每次拆分后要重新检查所有组,直到稳定。
给定文法 $G$:
E → E + T | T T → T * F | F F → ( E ) | id
(1) 消除左递归;(2) 求 First/Follow;(3) 构造 LL(1) 预测分析表;(4) 分析串 id + id * id。
(1) 消除左递归
$E \to T E'$,$E' \to + T E' \mid \varepsilon$
同理 $T \to F T'$,$T' \to * F T' \mid \varepsilon$
改写后文法:
E → T E' E' → + T E' | ε T → F T' T' → * F T' | ε F → ( E ) | id
(2) First / Follow
| 非终结符 | First | Follow |
|---|---|---|
| E | { (, id } | { ), $ } |
| E' | { +, ε } | { ), $ } |
| T | { (, id } | { +, ), $ } |
| T' | { *, ε } | { +, ), $ } |
| F | { (, id } | { *, +, ), $ } |
计算技巧:First 自底向上(从 F 开始);Follow 自顶向下,规则:把 $\text{Follow}(A)$ 加到产生式右部最右非终结符的 Follow,遇 $\varepsilon$ 则继续传递。
(3) LL(1) 预测分析表
对 $A \to \alpha$,凡 $a \in \text{First}(\alpha)$,填 $M[A,a] = A \to \alpha$;若 $\varepsilon \in \text{First}(\alpha)$,对 $b \in \text{Follow}(A)$ 填 $M[A,b] = A \to \alpha$。
| id | + | * | ( | ) | $ | |
|---|---|---|---|---|---|---|
| E | E→TE' | E→TE' | ||||
| E' | E'→+TE' | E'→ε | E'→ε | |||
| T | T→FT' | T→FT' | ||||
| T' | T'→ε | T'→*FT' | T'→ε | T'→ε | ||
| F | F→id | F→(E) |
表中每格至多一项 → 文法是 LL(1)。
(4) 分析 id + id * id $
| 栈 | 输入 | 动作 |
|---|---|---|
| $ E | id+id*id$ | E→TE' |
| $ E' T | id+id*id$ | T→FT' |
| $ E' T' F | id+id*id$ | F→id |
| $ E' T' id | id+id*id$ | 匹配 id |
| $ E' T' | +id*id$ | T'→ε |
| $ E' | +id*id$ | E'→+TE' |
| $ E' T + | +id*id$ | 匹配 + |
| $ E' T | id*id$ | T→FT' |
| ... | ... | (重复,最终栈空、输入 $) |
给定文法(已拓广):
S' → S S → AA A → aA | b
(1) 构造 LR(0) 项集族;(2) 构造 SLR(1) 分析表;(3) 分析串 aabb。
(1) LR(0) 项集族
$\text{GOTO}(I_0, S)$ = I1 = $\{ S' \to S\cdot \}$
$\text{GOTO}(I_0, A)$ = I2 = $\{ S \to A\cdot A,\; A \to \cdot aA,\; A \to \cdot b \}$
$\text{GOTO}(I_0, a)$ = I3 = $\{ A \to a\cdot A,\; A \to \cdot aA,\; A \to \cdot b \}$
$\text{GOTO}(I_0, b)$ = I4 = $\{ A \to b\cdot \}$
$\text{GOTO}(I_2, A)$ = I5 = $\{ S \to AA\cdot \}$
$\text{GOTO}(I_2, a)$ = I3;$\text{GOTO}(I_2, b)$ = I4
$\text{GOTO}(I_3, A)$ = I6 = $\{ A \to aA\cdot \}$
$\text{GOTO}(I_3, a)$ = I3;$\text{GOTO}(I_3, b)$ = I4
共 7 个状态:I0~I6。
(2) SLR(1) 分析表
$\text{Follow}(S) = \{\$\}$, $\text{Follow}(A) = \{a, b, \$\}$(因为 $S \to AA$,$A$ 既可在 $S$ 首位也可在末位)。
| 状态 | a | b | $ | S | A |
|---|---|---|---|---|---|
| 0 | s3 | s4 | 1 | 2 | |
| 1 | acc | ||||
| 2 | s3 | s4 | 5 | ||
| 3 | s3 | s4 | 6 | ||
| 4 | r3 | r3 | r3 | ||
| 5 | r1 | ||||
| 6 | r2 | r2 | r2 |
产生式编号:1: S→AA;2: A→aA;3: A→b。
(3) 走栈分析 aabb$
| 状态栈 | 符号栈 | 剩余输入 | 动作 |
|---|---|---|---|
| 0 | aabb$ | s3 | |
| 0 3 | a | abb$ | s3 |
| 0 3 3 | aa | bb$ | s4 |
| 0 3 3 4 | aab | b$ | r3 (A→b) |
| 0 3 3 6 | aaA | b$ | r2 (A→aA) |
| 0 3 6 | aA | b$ | r2 (A→aA) |
| 0 2 | A | b$ | s4 |
| 0 2 4 | Ab | $ | r3 (A→b) |
| 0 2 5 | AA | $ | r1 (S→AA) |
| 0 1 | S | $ | acc ✓ |
设计 S-属性 SDD,将算术表达式翻译为后缀表示。
SDD 设计
| 产生式 | 语义规则 |
|---|---|
| $L \to E$ | $L.\text{val} := E.\text{val}$ |
| $E \to E_1 + T$ | $E.\text{val} := E_1.\text{val} \| T.\text{val} \| \text{"+"}$ |
| $E \to T$ | $E.\text{val} := T.\text{val}$ |
| $T \to T_1 * F$ | $T.\text{val} := T_1.\text{val} \| F.\text{val} \| \text{"*"}$ |
| $T \to F$ | $T.\text{val} := F.\text{val}$ |
| $F \to ( E )$ | $F.\text{val} := E.\text{val}$ |
| $F \to \text{digit}$ | $F.\text{val} := \text{digit.lexval}$ |
$\|$ 表示字符串拼接。该 SDD 仅含综合属性,是 S-属性 → 可在 LR 分析中边归约边求值。
翻译示例:9 - 5 + 2
(按 + 改 -,规则同理)解析树后序遍历:先 9,再 5,得 "9 5 -";再 2,得 "9 5 - 2 +"。
将下列代码翻译为三地址码:
if (a < b) x = 1; else x = 2; while (x < 10) x = x + 1;
翻译结果
100: if a < b goto 103
101: goto 105 ; B.falselist = {101}
102: ; (未用,可省)
103: x = 1 ; then 部分,B.truelist 回填到此
104: goto 107 ; jump over else
105: x = 2 ; else 部分,B.falselist 回填到 105
106: ; (未用)
107: ; while 入口
108: if x < 10 goto 110
109: goto 112
110: x = x + 1
111: goto 107 ; loop back
112: ; while 出口
回填要点
- if:B.truelist → then 起始;B.falselist → else 起始(无 else 则 → S 出口)。
- while:B.truelist → 循环体起始;循环体末尾 goto 测试入口;B.falselist → S 出口。
- 关键:M 标记当前 nextquad,N 用于跳过 else 部分。
判断文法 $S \to Aa \mid bAc \mid dc \mid bda$; $A \to d$ 是否为 SLR / LALR / LR(1)。
分析
通过 GOTO 路径:
• 路径 $S \to Aa$:$d$ 在 $A$ 推出,期望后跟 $a$ → $[A \to d\cdot, a]$
• 路径 $S \to bAc$:$d$ 在 $A$ 推出,期望后跟 $c$ → $[A \to d\cdot, c]$
• 路径 $S \to dc$:直接的 $[S \to d\cdot c]$
• 路径 $S \to bda$:$[S \to bd\cdot a]$
SLR(1):$\text{Follow}(A) = \{a, c\}$。在含 $[A \to d\cdot]$ 的状态中,对 $a$、$c$ 都填归约。但同状态可能还有 $[S \to d\cdot c]$(移进 $c$),导致移进/归约冲突。
LR(1):$[A \to d\cdot, a]$ 和 $[S \to d\cdot c, \$]$ 在不同 LR(1) 状态(展望符不同 → 路径不同 → 状态不同),无冲突。
LALR(1):合并同心项集后,$[A \to d\cdot, a]$ 和 $[A \to d\cdot, c]$ 在同一状态,同时 $[S \to d\cdot c]$ 也合并进来。对 $c$:既归约($A \to d$)又移进(→ $S \to dc$),冲突。
结论:仅 LR(1) 文法。
第 16 章 · 互动自测题(带打分) 自编练习
共 30 道选择/判断题,覆盖全部 7 章。题目基于课件知识点整理,不是课件原文。
📝 知识点自测
第 17 章 · FIRST / FOLLOW 计算器 辅助工具
输入文法(每行一条产生式),自动计算每个非终结符的 FIRST 和 FOLLOW 集。
格式:A -> α | β,终结符小写或符号,非终结符大写。空串用 ε 或 epsilon。第一行的左部默认为开始符号。
输入文法
结果
第 18 章 · 考前速查表 整理速查
15.1 易错陷阱(必背!)
不是 NFA 更强!
CFG 能描述所有正则语言。
LL 才需要。
只有 S/R、R/R。
区别在归约时是否用 $\text{Follow}$。
不是最左!
但推导步骤本身不同。
合并同心项集。
LR 更强大。
15.2 公式备忘
消左递归: $A \to A\alpha \mid \beta \Rightarrow A \to \beta A',\; A' \to \alpha A' \mid \varepsilon$
提左公因子: $A \to \alpha\beta \mid \alpha\gamma \Rightarrow A \to \alpha A',\; A' \to \beta \mid \gamma$
LR(1) 闭包:$[A \to \alpha\cdot B\beta, a]\Rightarrow [B\to\cdot\gamma, b],\; b\in\mathrm{First}(\beta a)$
15.3 时间分配建议(总分 100,2 小时)经验建议
| 题型 | 分值 | 建议时间 |
|---|---|---|
| 选择 + 多选 | 40 | 30 min |
| 简答 | 10-25 | 15 min |
| RE/自动机 | 15 | 20 min |
| LL 分析 | 15 | 20 min |
| LR 分析 | 20-25 | 30 min |
| 检查 | — | 5 min |
15.4 临考心态 经验建议
- LR 大题的 LR(0)/LR(1) 项集族是纯机械工作,不要慌,按部就班。
- First/Follow 计算时,先按依赖顺序(底层 → 顶层)计算 First,再逆序计算 Follow。
- 正则表达式构造题,先用 DFA 思考,再反推 RE。
- "句柄"题先在脑中做最右推导,倒数第一步用的产生式右部就是。