- 第一章 引论
- 1.1 编译器
- 1.1.1 什么是编译器
- 1.1.2 编译器和解释器
- 1.1.3 编译器的原则
- 1.2 编译器的结构
- 1.2.1 内部结构
- 1.2.2 两阶段和三阶段结构
- 1.2.3 流水线结构的思想
- 1.2.4 常见的编译器结构
- 1.3 小结
- 1.4 编译器实例
- 1.5 思考:编译器什么性质最重要?
- 第二章 文法
- 2.1 基本概念
- 2.1.1 字母表及其运算
- 2.1.2 字符串及其运算
- 2.2 文法和语言
- 2.2.1 直观概念
- 2.2.2 定义
- 2.2.3 相关推导
- 2.2.4 句型与句子
- 2.2.5 语言
- 2.3 文法的类型
- 2.3.1 分类
- 2.3.2 举例
编译器是一个程序,核心功能是把源代码翻译成目标代码。
源代码:
- C/C++、Java、C#、html、SQL、ML、FORTRAN…
目标代码:
- x86、IA64、ARM、MIPS, …
- “目标”语言通常指的是某种处理器的指令集
不仅翻译成某个具体机器上的目标代码,还可以翻译成某种另外的源代码;很多研究性编译器产生 C 程序作为输出,因为大多计算机都有 C 编译器可用;C++,可用编译器 cfront,翻译成 C 代码,再由 gcc 翻译成 x86 目标代码执行。
其他系统也可视为编译器:
如,可认为产生 PostScript 的排版程序是一个编译器
- 输入:文档在印刷纸面上如何布局的规格
- 输出:产生的PostScript文件(PostScript是一种描述图像的语言)
类似的 Latex 也是。
1.1.2 编译器和解释器 解释程序接受某个语言的程序并立即运行这个源程序。它的工作模式是一个个地获取、分析并执行源程序语句,一旦第一个语句分析结束,源程序便开始运行并且生成结果,它特别适合程序员以交互方式工作的情况,即希望在获取下一个语句之前了解每个语句的执行结果,允许执行时修改程序。
区别:
- 编译器先将源代码编译成目标代码,然后执行;解释器不生成目标代码,而是将源代码解释并直接运行。
- 解释程序的速度非常慢(有时甚至慢 100 倍),并且空间开销大;但是允许执行时修改程序。

举例:
- 编译器:C、C++…
- 解释器:Perl、Scheme、SQL…
- 编译器 / 解释器:java

- 语义相同
- 以某种可察觉的方式改进输入程序

-
IR: 中间表示(Intermediate Representation),编译器使用一些数据结构来表示它处理的代码,这种形式称为 IR;
-
前端: 将输入的代码映射到 IR;
处理和输入相关的部分,理解源语言程序,是什么语言、有什么样语法规则、满足什么约束条件等;依赖于源语言,与目标机器无关。
-
后端: 将 IR 映射到目标机的指令集和有限的资源上,处理前端生成的 IR;
将程序映射到目标机器,关心目标机器有哪些指令集、指令集有什么样约束、前端语法结构怎么样映射到指令集;依赖于目标机,一般与源语言无关。
(1)两阶段结构

简化编译器重定目标的过程(改变编译器使之针对新处理器生成代码的任务,通常称为将该编译器重定目标)。
- 想象为单个前端构建多个后端,可产生输入同一语言但输出针对不同目标机器的编译器。类似地,可以想象针对不同语言的前端生成同样的 IR 并使用共同的后端。
- IR 可以服务于几种源和目标的组合。
(2)三阶段结构

优化器负责分析并转换 IR,改进 IR,是一个 IR 到 IR 的转换器。优化器可以对 IR 处理一遍或多遍,分析 IR 并重写 IR。
1.2.3 流水线结构的思想 编译器可看成多个阶段构成的“流水线”结构。

好处:
- 高级语言相对目标机器非常高层,通过一遍直接到达目标机器,较难,将这个过程切分为不同阶段,可以逐渐接近目标语言。
- 从软件工程角度,实际上编译器规模庞大,很复杂,都写到一个模块里,很难实现和维护。

-
词法分析
从左到右一个字符一个字符地读入源程序,从构成源程序的字符流进行扫描和分析,从而识别出一个个单词(符号)。
-
语法分析
将单词序列分解成各类语法短语。
-
语义分析
审查源程序有无语义错误,为代码生成阶段收集类型信息。
-
中间代码生成
将源程序转换成内部表示形式(中间代码),这是一种结构简单、含义明确的记号系统。设计原则是:
- 容易生成
- 容易将其翻译成目标代码
-
代码优化
对前一阶段产生的中间代码进行变换或进行改造,使其更高效。
-
目标代码生成
把中间代码变换成特定机器上的绝对指令代码或可重定位的指令代码或汇编指今代码。
(1)未优化的结构

符号表: 储存编译过程的重要信息
字符序列: 程序代码
记号序列: 记号序列会被语法分析处理,检查语法是否合法,在内存中会建立抽象语法树
抽象语法树: 变量是否声明、函数是否有
定义、参数是否对应一致
中间代码: 三地址代码、SSA、控制流图等
(2)更复杂的结构

指令选择: 对高层语法结构选择合适的汇编指令进行实现,完成语法结构到汇编的映射,得到抽象汇编:有的寄存器指定了,有的没有指定;后续几个阶段是做优化
控制流分析: 构建另一个中间表示控制流图,交给寄存器分析阶段进行寄存器指派
1.3 小结 编译器由多个阶段组成,每个阶段都要处理不同的问题。
- 使用不同的理论、数据结构和算法
因此,编译器设计中的重要问题是如何合理的划分组织各个阶段。
-
接口清晰
-
编译器容易实现、维护
(1)源语言 Sum 的实现
源语言:Sum -->
目标机器:操作数栈、两条指令(push、add)


对应的指令:push 1
push 2
add
push 3
add
增加一个代码优化的阶段,使得不用生成代码运行求结果,而是静态直接输出 6:

(2)执行代码的优化
考虑代码:a -> a * 2 * b * c * d
整体框架步骤:

-
词法分析器
-
将字符构成的串转换为单词构成的流;
-
通常基于词类来引用单词,标识输入程序中的各个单词,并将每个单词归入对应的词类。
例如:对于语句“Compilers are engineered objects.”:
-
n o u n ’Compilers’ v e r b ’are’ a d j e c t i v e ’engineered’ n o u n ’objects’ e n d m a r k ’.’ \begin{array}{c} \hline noun & \text{'Compilers'} \\ verb & \text{'are'} \\ adjective & \text{'engineered'} \\ noun & \text{'objects'} \\ endmark & \text{'.'} \\ \hline \end{array} nounverbadjectivenounendmark’Compilers’’are’’engineered’’objects’’.’
-
语法分析器
判断输入流是否是源语言的一个句子。
语法规则:
进行推导:
-
类型检查
检查输入程序中对名字的使用在类型方面是否一致。
例如,若 b 和 d 是字符串,则该语句无效。
-
中间表示
前端处理的最后一个问题是生成代码的 IR 形式,IR 可以表示为图,或类似于有序的汇编代码程序。

-
优化器:分析+转换
改进后,乘法次数从 4n 下降到 2n+2:

-
指令选择
将每个 IR 操作在各自的上下文中映射为一个或多个目标机操作。

- 这里假定寄存器的数目是不受限制的
- 用符号名来命名寄存器,如 ra 包含了 a,rarp 包含了一个地址。这些符号寄存器名,或称为虚拟寄存器,需映射到目标机的实际寄存器
另外,指令选择器可以利用目标机提供的特殊操作,如:如果有立即数乘法操作(multI)可用,编译器会将 mult ra, r2 ⇒ ra 替换为 multI ra, 2 ⇒ ra,这样就不必要进行 loadI 2 ⇒ r2 操作了,而且减少了对寄存器的使用。
- 寄存器分配
将虚拟寄存器映射到实际的目标机寄存器。

- 指令调度
为产生执行快速的代码,代码生成器可能需要重排操作。
假定目前 loadAI 或 storeAI 操作需要三个周期,mult 操作需要两个周期,而所有其他操作都只需一个周期。


作为一个软件系统,编译器也有很多方面的性质:
- 编译的效率,即编译一个程序的速度和代价;
- 编译生成代码的效率,即编译器生成的可执行程序的运行效率;
- 编译生成代码的质量,即和源代码语义等价性;
(1)定义
字母表也叫符号集或字符集:
- 字母表 Σ \Sigma Σ 是元素的非空集合;
- 字母表中的元素称为符号(字母、数字、标点等)
(2)字母表的乘积 Σ 1 Σ 2 = { a b ∣ a ∈ Σ 1 , b ∈ Σ 2 } { 0 , 1 } { a , b } = { 0 a , 0 b , 1 a , 1 b } \Sigma_{1}\Sigma_{2}=\{ab\ |\ a\in\Sigma_{1},\ b\in\Sigma_{2}\} \\\\ \{0,\ 1\}\{a,\ b\}=\{0a,\ 0b,\ 1a,\ 1b\} Σ1Σ2={ab ∣ a∈Σ1, b∈Σ2}{0, 1}{a, b}={0a, 0b, 1a, 1b} (3)字母表的幂 { Σ 0 = { ε } Σ n = Σ n − 1 Σ , n ≥ 1 { 0 , 1 } 3 = { 000 , 001 , 010 , 011 , 100 , 101 , 110 , 111 } \left\{ \begin{array}{l} \Sigma^{0}=\{\varepsilon\}&\\ \Sigma^{n}=\Sigma^{n-1}\Sigma,&n\ge1 \end{array} \right. \\\\ \{0,\ 1\}^{3}=\{000,\ 001,\ 010,\ 011,\ 100,\ 101,\ 110,\ 111\} {Σ0={ε}Σn=Σn−1Σ,n≥1{0, 1}3={000, 001, 010, 011, 100, 101, 110, 111} (4)字母表的闭包
正闭包: Σ + = Σ ∪ Σ 2 ∪ Σ 3 ∪ … \Sigma^{+}=\Sigma\cup\Sigma^{2}\cup\Sigma^{3}\cup\dots Σ+=Σ∪Σ2∪Σ3∪… (柯林)闭包: Σ ∗ = Σ 0 ∪ Σ ∪ Σ 2 ∪ Σ 3 ∪ … \Sigma^{*}=\Sigma^{0}\cup\Sigma\cup\Sigma^{2}\cup\Sigma^{3}\cup\dots\\ Σ∗=Σ0∪Σ∪Σ2∪Σ3∪…
2.1.2 字符串及其运算(1)定义
由字母表中的符号组成的任何有穷序列称为符号串。
如果某符号串 x 中有 m 个符号,则称其长度为 m,表示为 |x| = m。
空符号串:不包含任何符号的符号串,用 ε 表示,其长度为 0,即 |ε| = 0。
(2)字符串集合
若集合 A 中的一切元素都是某字母表上的符号串,称 A 为该字母表上的符号串集合。
(3)字符串的头尾
假设 z = xy 是一个符号串,那么 x 是 z 的头 / 前缀,y 是 z 的尾 / 后缀。
(4)字符串的连接
设 x 和 y 是符号串,连接 xy 是把 y 的符号写在 x 的符号之后得到的符号串。
(5)字符串的方幂
设 x 是符号串,把 x 自身连接 n 次得到符号串 z,即 z = xx…xxx,称为符号串 x 的方幂,写作 z = xn。
(6)字符串集合的乘积 / 连接 A B = { x y ∣ x ∈ A & y ∈ B } A = { a , b } , B = { c , d } ⟹ A B = { a c , a d , b c , b d } AB=\{xy\ |\ x\in A\ \&\ y\in B\} \\\\ A=\{a,\ b\},\ B=\{c,\ d\}\Longrightarrow AB=\{ac,\ ad,\ bc,\ bd\} AB={xy ∣ x∈A & y∈B}A={a, b}, B={c, d}⟹AB={ac, ad, bc, bd}
2.2 文法和语言 2.2.1 直观概念 自然语言:句子的构成规则。
::=
::= |
::= 我 | 你 | 他
::= 王明 | 大学生 | 工人 | 英语
::=
::= 是 | 学习
::= |
这些规则成为判别句子结构是否合法的依据,这样的语言描述称为 文法。
2.2.2 定义 文法 G 定义为四元组:(T,N,P,S)
-
T:终结符集合
或记为 VT,是文法所定义的语言的基本符号,可称为单词、token。
-
N:非终结符集合
或记为 VN,是用来表示语法成分的符号,称为语法变量。
-
P:规则集合
表示方式: α → β \alpha\rightarrow\beta α→β 或 α : : = β \alpha ::=\beta α::=β,称为“ α \alpha α 定义为 β \beta β”、“ α \alpha α 推出 β \beta β”。
其中:
- α ∈ ( N ∪ T ) ∗ \alpha\in(N\cup T)^{*} α∈(N∪T)∗,且至少包含一个非终结符
- β ∈ ( N ∪ T ) ∗ \beta\in(N\cup T)^{*} β∈(N∪T)∗
-
S:识别符(开始符)
识别符或开始符,非终结符,至少在一条规则中作为左部出现。
开始符号,文法中最大的语法成分,可将文法 G 写成 G[S]。
(1)一些约定
-
括起来的是非终结符,不用尖括号括起来是终结符;
大写字母表示非终结符,小写字母表示终结符。
-
规则中 “ → \rightarrow →” 有时使用 “ : : = ::= ::=”。
-
对一组有相同左部的 α \alpha α 产生式: α → β 1 , α → β 2 , … , α → β n \alpha\rightarrow\beta_{1},\ \alpha\rightarrow\beta_{2},\dots,\ \alpha\rightarrow\beta_{n} α→β1, α→β2,…, α→βn 可以简记为: α → β 1 ∣ β 2 ∣ … β n \alpha\rightarrow\beta_{1}\ |\ \beta_{2}\ |\ \dots\beta_{n} α→β1 ∣ β2 ∣ …βn 其中, β i \beta_{i} βi 称为候选式。
(2)终结符 / 非终结符的常见形式
- 终结符的常见情况:
- 小写字母,如 a、b、c
- 运算符,如 +、* 等
- 标点符号,如逗号、括号等
- 数字 0、1、2 等
- 粗体字符串,如 id、if 等
- 非终结符的常见情况:
- 大写字母,如 A、B、C
- 字母 S,通常表示开始符号,一般第一个产生式的左部就是开始符号
- 小写、斜体的名字,如 expr、stmt 等
- 代表程序构造的大写字母,如 E(表达式)、T(项)和 F(因子)等
(1)直接推导
给定文法 G = ( V T , V N , P , S ) G=(V_{T},\ V_{N},\ P,\ S) G=(VT, VN, P, S) 如果 α → β ∈ P \alpha\rightarrow\beta\in P α→β∈P 那么可以将符号串 γ α δ \gamma\alpha\delta γαδ 中的 α \alpha α 替换为 β \beta β,记作: γ α δ ⟹ γ β δ \gamma\alpha\delta\Longrightarrow\gamma\beta\delta γαδ⟹γβδ 此时我们称: γ α δ \gamma\alpha\delta γαδ 直接产生 / 推导出 γ β δ \gamma\beta\delta γβδ,或者 γ β δ \gamma\beta\delta γβδ 直接归约到 γ α δ \gamma\alpha\delta γαδ。
(2)推导
若存在 α 0 ⇒ α 1 ⇒ α 2 ⇒ α 3 ⇒ ⋯ ⇒ α n \alpha_{0}\Rightarrow\alpha_{1}\Rightarrow\alpha_{2}\Rightarrow\alpha_{3}\Rightarrow\dots\Rightarrow\alpha_{n} α0⇒α1⇒α2⇒α3⇒⋯⇒αn 则称符号串 α 0 \alpha_{0} α0 经过 n 步推导可以得出 α n \alpha_{n} αn,简记为: α 0 ⇒ n α n \alpha_{0}\Rightarrow^{n}\alpha_{n} α0⇒nαn
- α ⇒ 0 α \alpha\Rightarrow^{0}\alpha α⇒0α
- ⇒ + \Rightarrow^{+} ⇒+:经过正整数步推导
- ⇒ ∗ \Rightarrow^{*} ⇒∗:经过若干步(可以为 0)推导

(1)句型
如果 S ⇒ ∗ α , α ∈ ( V T ∪ V N ) ∗ S\Rightarrow^{*}\alpha,\ \alpha\in(V_{T}\cup V_{N})^{*} S⇒∗α, α∈(VT∪VN)∗ 则称 α \alpha α 是 G 的一个 句型。
(2)句子
如果 S ⇒ ∗ ω , ω ∈ V T ∗ S\Rightarrow^{*}\omega,\ \omega\in V_{T}^{*} S⇒∗ω, ω∈VT∗ 则称 α \alpha α 是 G 的一个 句子。
(3)区别
- 一个句型既可以包含非终结符,也可以包含终结符,也可是空串
- 句子是不包含非终结符的句型,只有终结符

由文法 G 的开始符号 S 推导出的所有句子构成的集合称为文法 G 生成的语言,记为 L(G)。 L ( G ) = { ω ∣ S ⇒ ∗ ω , ω ∈ V T ∗ } L(G)=\{\omega\ |\ S\Rightarrow^{*}\omega,\ \omega\in V_{T}^{*}\} L(G)={ω ∣ S⇒∗ω, ω∈VT∗}
2.3 文法的类型 2.3.1 分类 从 0 到 3 一共四类文法,内层是外层的子集。

-
0 型文法:任意文法
-
1 型文法:上下文有关文法
-
除 S → ε S\rightarrow\varepsilon S→ε 外,每个产生式 α → β \alpha\rightarrow\beta α→β 均满足 ∣ α ∣ ≤ ∣ β ∣ |\alpha|\le|\beta| ∣α∣≤∣β∣。
-
有些定义中将产生式描述为: α 1 A α 2 → α 1 β α 2 \alpha_{1}A\alpha_{2}\rightarrow\alpha_{1}\beta\alpha_{2} α1Aα2→α1βα2
表示只有 A 出现在上下文中,才允许取代 A,体现上下文有关。
-
-
2 型文法:上下文无关文法
- 对于产生式 A → β A\rightarrow\beta A→β, A A A 是一个非终结符。
- A → β A\rightarrow\beta A→β,用 β \beta β 取代非终结符 A A A 时,与 A A A 所在的上下文无关

-
3 型文法:正规 / 正则文法
每个产生形式都是:
- A → a B A\rightarrow aB A→aB 或 A → a A\rightarrow a A→a(左线性文法)
- A → B a A\rightarrow Ba A→Ba 或 A → a A\rightarrow a A→a(右线性文法)
(大写字母为非终结符,小写字母为终结符)

文法 G = ({S, A, B}, {0, 1}, P, S),其中 P 由下列产生式组成: S → 0 A ; A → 0 A ; B → 1 B ; S → 1 B ; A → 0 S ; B → 1 ; S → 0 ; A → 1 B ; B → 0 ; \begin{array}{l} S\rightarrow0A; \quad & A\rightarrow0A; \quad & B\rightarrow1B;\\ S\rightarrow1B; \quad & A\rightarrow0S; \quad & B\rightarrow1;\\ S\rightarrow0; \quad & A\rightarrow1B; \quad & B\rightarrow0; \end{array} S→0A;S→1B;S→0;A→0A;A→0S;A→1B;B→1B;B→1;B→0;