编译原理知识点整理
语言(Language)
语言研究包括如下三个方面。
- 语法(Syntax):语言的正确形式;
- 语义(Semantic):语言的含义;
- 语用(Pragmatics):语言和使用者的关系。
编程语言(Programming Language)
可通过时期对编程语言进行如下分类。
机器语言使用计算机唯一能够识别的二进制代码指令来编写程序的语言,不同的硬件对应不同的机器语言,因而不易移植。
汇编语言又称符号语言,比机器语言直观,更易理解和记忆。
汇编器(Assembler)
将汇编程序替换为机器码。
汇编器生成的文件称为目标文件(Object File),或二进制文件(Binaries)。
助记符(Mnemonics)指为每个操作码分配的简单记号。助记符后紧跟操作数(Operand),形成完整指令。汇编程序即这些指令的集合,和底层硬件紧密联系。起初,汇编语言的指令只是机器指令的助记表示,后来,宏指令被加入到汇编语言中。
链接器(Linker)
将多个目标文件外加库链接为一个可执行文件,涉及符号解析和重定位工作。
链接的时机如下。
- 编译时:静态链接器负责;
- 加载时:加载器负责;
- 运行时:动态链接器负责。
加载器(Loader)
将可执行文件放到内存中执行。
编译器(Compiler)
将源语言程序转换为等价的、更为低级的目标语言程序。
Assembly languages have direct, one-to-one mapping to machine instruction. But, a single line of a high-level programming language might result in dozens of instructions.
编译的大体过程如下。
- 词法分析;
- 语法分析;
- 语义分析(Semantic Analysis);
- 中间代码生成(Intermediate Code Generation);
- 代码优化(Code Optimizer);
- 目标代码生成(Object Code Generation)。
贯穿其中的两个逻辑部分如下。
- 符号表管理;
- 错误处理。
对输入文件从头到尾扫视,完成预定处理的过程,称为一遍(Pass)或一趟。
解释器(Interpreter)
直接利用用户提供的输入执行源程序中指定的操作。
在把用户输入映射为输出的过程中,由编译器产生的机器语言目标程序通常较解释器快很多;然而解释器的错误诊断效果通常比编译器更好,因为它逐个语句地执行源程序。
解释、编译、汇编都可称为翻译(Translate),即将源语言转换为目标语言。
预处理器(Preprocessor)
将源程序聚合在一起,将宏转换为源语言。
乔姆斯基谱系(Chomsky Hierarchy)
乔姆斯基将文法分为如下四种类型。
其中,四种文法对应的四种语言有关系
。
类型 | 名称 | 产生式 |
---|---|---|
0型文法 | 递归可枚举文法 | |
1型文法 | 上下文有关文法 | 在0型文法的基础上满足 |
2型文法 | 上下文无关文法 | |
3型文法 | 正规文法、右线性文法 | 满足 |
巴科斯范式(BNF; Backus-Naur Form)
即上下文无关文法(Context Free Grammar),由约翰·巴科斯等提出。
文法(Grammar)以更广泛的方式描述词法和语法,可以说是以有穷集合刻画无穷集合的工具。
文法
- 终结符(Terminal)集合
; - 字母表:非空有穷的基本符号集
,因此字母表中的元素称为符号; - 符号串:由符号组成的任何有穷序列。 > 空符号串用
表示,长度为0。 符号串的 次方幂即把符号串重复写 次。 即 的闭包,表示 上所有有穷长的符号串的集合。 而 称为 的正闭包。
- 字母表:非空有穷的基本符号集
- 非终结符(Nonterminal)集合
; - 产生式(Production)集合
,形式如下; 其中 读作「定义为」或「可具有此般形式」。 - 指定一个非终结符为开始符号
。
推导(Derivation)
即从开始符号起,不断将某个非终结符替换为非终结符的某个产生式的结果。
直接推导记为
归约(Reduction)即推导的逆过程。
语言可定义为推导所得所有终结符串的集合。
代表性的文法
如下的文法指明了运算符的结合性和优先级,常被用作示例。
词法分析(Lexical Analysis)
将源代码分割为若干个词法单元的处理,又称扫描(Scanning)。
词法单元(Token)
又称记号,由一个词法单元名和一个可选的属性值组成。
词法单元在形式上可表示为二元组
。
词法单元的常见类别有关键字、标识符、字面量、运算符和界符等。
词素(Lexeme)
源程序中的字符序列,与某个词法单元的模式匹配。
词法分析器将词素分析为词法单元的实例,并发送至语法分析器以回应其下一个词法单元的请求。
将词法分析工作分离的考虑如下。
- 使整个编译程序的结构更简洁、清晰和条理化;
- 编译程序的效率会改进;
- 增强编译程序的可移植性。
词法单元的形式化描述工具有正规文法、正则表达式和有穷自动机。
有穷自动机(Finite Automata)
模式的识别装置。
确定有穷自动机(DFA; Deterministic Finite Automata)
- 有穷状态集
; - 输入符号字母表
; - 转换函数
,是 的映像,即定义状态关于输入符号的后继状态; - 初态
; - 终态集
,终态又称可接受(Accept)状态。
不确定有穷自动机(NFA; Nondeterministic Finite Automata)中,
- 转换函数
,是 的映像,即输入可为符号串,且相同输入的后继状态可有复数个; - 非空初态集
。
显然DFA是NFA的特例。对于每个NFA都存在一个与之等价的DFA。
子集构造(Subset Construction)算法
将NFA转换为等价的DFA,即NFA的确定化。
其中,子集指NFA状态的子集。
子集构造算法中的函数定义如下。
操作 | 描述 |
---|---|
能够从NFA的状态 |
|
即 |
|
能够从 |
子集构造算法的伪代码如下。
SUBSET-CONSTRUCTION 输入:一个NFA 输出:一个接受同样语言的DFA |
---|
一开始, |
任何正则语言都有一个唯一的状态数目最少的DFA,称最小确定有穷自动机(Minimal DFA)。
无用状态(Dead State)
无法到达或无法由此状态到达终态的状态,又称「死状态」。
无用状态可以非常简单地被消除。
等价状态(Non-distinguishable State)
DFA的最简化中需要合并的状态,又称「不可区分状态」。
两个状态等价的条件如下。
- 一致性条件:两个状态必须同时为可接受状态或不可接受状态;
- 蔓延性条件:对于所有输入符号,两个状态必须转换到等价的状态里。
可以通过不断分割,将DFA的状态分割为不相交的子集,使任两个来自不同的子集的状态都是可区分的,任两个来自同一子集中的状态都是等价的,找到并合并等价状态,以实现DFA的最小化。
lex
自动生成词法分析器的工具,通过输入扩展名为.l的文件,输出词法分析器的C语言代码。
flex是增强版的lex,其中f指快速(Fast)。
一个lex程序具有如下形式。
1 | /* 定义区块 */ |
下例定义匹配实数模式串并返回实数的规则。
1 | ([1-9][0-9]*)|0|([0-9]+\.[0-9]*) { |
下例忽略代码中的空白。
1 | [ \t] ; |
若匹配失败则出现词法错误,结束程序。
.
用于匹配除换行符\n
外的任何字符。
1 | . { |
语法分析(Syntax Analysis)
从词法单元构建语法树的处理,又称解析(Parsing)。
抽象语法树(AST; Abstract Syntax Tree)
又称语法树,包含代码中所有词法单元。
语法树涉及的概念如下。
- 句型:在语法树生长过程的任何时刻,所有叶子结点的排列;
- 短语:完整语法树中一棵子树的所有叶子排列;
- 直接短语:限制子树为仅有父子两代的一棵子树;
- 句柄:最左的直接短语。
自顶向下分析
从文法的开始符号出发,根据当前的输入符号唯一地确定使用的产生式以继续推导。
在上下文无关文法
操作 | 描述 |
---|---|
LL(k)文法
从左向右扫描输入串,使用最左推导,需向前看
通常采用
文法。
LL(1)文法
其中
, 和 是 的两个不同产生式。
某些非
提取左公因子;
对于产生式
可改写为如下形式。 不含左公因子只是
文法的必要条件,而非充分条件。 消除直接左递归。
对于产生式
可改写为如下形式。 将前述代表性的文法消除直接左递归,得到如下形式。
要消除间接左递归,需通过产生式置换非终结符,转换为直接左递归后再消除。
预测分析表(Transition/Parsing Table)
以前述代表性的文法为例,求出各非终结符的
Nonterminal | Nullable? | ||
---|---|---|---|
✓ | |||
✓ | |||
再求出各产生式的
最后构造预测分析表
Nonterminal | ||||||
---|---|---|---|---|---|---|
自底向上分析
LR(k)文法
向右查看
通常
。
LR(0)文法
项(Item)
在文法
产生式
对空产生式
有且仅有一个项即 。
增广文法(Augmented Grammar)
在原文法
文法的规范
为了构造文法的规范
其中
是文法 的一个项目集, 。
操作 | 描述 |
---|---|
1. 一开始,将 2.如果 3. 重复规则直至 |
|
即当前输入为 |
SLR(1)文法
S指Simple。
同时,
如前述代表性的文法存在项集
,不是 文法。
情况 | 规则 |
---|---|
移进 | |
使用规则 |
|
使用规则 |
yacc
自动生成语法分析器的工具,通过输入.y扩展名的文件,输出语法分析器的C语言代码。
yacc指Yet Another Compiler Compiler。
bison则是yacc的机能扩展版本,由GNU项目发布。
一个yacc程序具有如下形式。
1 | /* 定义区块 */ |
在.l和.y文件的首行C代码加入如下定义可将输入捕获为浮点数。
In .l file, this definition must take place before the
#include ".tab.h"
.
1 |
若要使yylval
能表示多种类型,在.y文件加入如下联合体声明。
1 | %union { |
使用%token<>
指定词法单元的类型。
1 | %token <double_value> LITERAL |
使用%type<>
指定非终结符的类型。
1 | %type <double_value> expr |
追加如下部分以实现错误恢复机制。
其中
error
是匹配错误的特殊记号,CR
指换行符\n
,yyclearin
丢弃预备读入的词法单元,yyerrok
通知yacc程序已经从错误状态恢复。
1 | line : ... |
在用户代码区块定义yyerror
函数,以输出错误的位置。
1 | int yyerror(const char* str) { |