650 likes | 881 Views
编译原理与技术. 课程总结. 课程目录. 第 1 章 概论 第 2 章 词法分析 第 3 章 语法分析 第 4 章 语法制导翻译生成中间代码 第 5 章 运行环境 第 6 章 代码生成. 第 1 章 概论. 编译与解释是语言翻译的两种基本形式. 1 .编译器: 先翻译后执行, 工作效率高,即时间快、空间省;交互性与动态特性差、可移植性差。大多数 PL 采用此种方法翻译; 2 .解释器: 边翻译边执行, 工作效率低,即时间慢、空间费;交互性与动态特性好、可移植性好。早期的 Basic 和现在的 Java 等。. 编译过程. 1 .编译器的工作过程:
E N D
编译原理与技术 课程总结
课程目录 第1章 概论 第2章 词法分析 第3章 语法分析 第4章 语法制导翻译生成中间代码 第5章 运行环境 第6章 代码生成
第1章 概论 编译与解释是语言翻译的两种基本形式 1.编译器:先翻译后执行,工作效率高,即时间快、空间省;交互性与动态特性差、可移植性差。大多数PL采用此种方法翻译; 2.解释器:边翻译边执行,工作效率低,即时间慢、空间费;交互性与动态特性好、可移植性好。早期的Basic和现在的Java等。
编译过程 1.编译器的工作过程: 词法分析、语法分析、语义分析 中间代码生成、代码优化 目标代码生成 符号表管理 出错处理 2.编译器的阶段
编译过程各阶段工作的归纳 <1> 词法分析:识别单词,至少分以下几大类:关键字、标识符、字面量、特殊符号; <2> 语法分析:得到语言结构并以树的形式表示; <3> 语义分析:考察结构正确的句子是否语义合法,可修改树结构; <4> 中间代码生成(可选):生成一种既接近目标语言,又与具体机器无关的表示,便于优化与代码生成; (编译器与解释器的以上工作阶段可以一致)
编译过程各阶段工作的归纳 <5> 中间代码优化(可选):局部优化、循环优化、全局优化等;实际上是一个等价变换,变换前后的指令序列完成同样的功能,而程序占用的空间和执行的时间都更省、更有效。 <6> 目标代码生成:不同形式的目标代码-汇编、可重定位、内存形式(Load-and-Go); <7> 符号表管理:合理组织符号,便于各阶段使用; <8> 出错处理:错误的种类-词法错、语法错、静态语义错、动态语义错。
编译器的分析/综合模式 1.前端:语言结构的分析 2.后端:语言意义的分析与处理 3.中间代码:前端与后端的分界
例题 1. 从程序运行的角度看,编译程序和解释程序的主要区别是 是否生成目标代码 。 2. 编译程序的基本组成有:词法分析、、、中间代码生成、代码优化、、和,其中,中间代码生成和代码优化是可选的;对表达式中运算数的类型检查一般在阶段进行。 3. 编译程序是对________。 A. 汇编语言的翻译 B. 高级语言的解释执行 C. 机器语言的执行 D. 高级语言的翻译
第2章 词法分析 词法分析的两个重要环节: 规定所有合法输入+识别合法输入 要点: <1> 什么是正规式?什么是正规集? <2> 什么是有限状态自动机? <3> 如何利用Thomson算法从正则表达式构建NFA? 如何利用子集法从NFA得到DFA? 如何将一个DFA最小化?
第2章 词法分析 要点: <4> 为什么要进行词法分析? <5>如何从描述设计出正规式? <6>正则表达式、有限自动机、NFA、DFA与词法分析器的 关系?
疑难点 • 为什么要进行词法分析? 词法分析的第一个目的是将输入的程序文本中所有符号分类,使得接下来的语法分析可以在分析语法的过程中不再关心程序文本的细节。举例来说:C语言语句: int a; int b 显然是两个不同的语句,但是对语法分析而言,它们是相同的,其格式都是: 类型名 标识符 (对语法分析器而言,变量的名称没有意义)。词法分析器的作用就是将这些不同的部分先去除,只将语法分析需要关心的部分整理出来,留待语法分析器处理。 语法分析的第二个目的是初步整理出符号表的内容,并将其留给语义分析器处理。
疑难点 • 如何从描述设计出正规式? 从描述生成正规式没有任何现成的、形式化的方法,只能依靠经验。 • 正则表达式、有限自动机、NFA、DFA与词法分析器的关系 ? 正则表达式是一种描述字符串组成的规则。有限自动机可以看作是一种利用状态转换表示字符串匹配过程的算法。NFA和DFA则是有限自动机工作过程的图形化表示。其中NFA可以利用Thomson算法与正则表达式建立一一对应的关系。而词法分析器则是将DFA表示的算法用计算机程序实现后得到的程序。词法分析器中可以直接用无限循环和switch-case语句将自身的状态与DFA中的状态图实现一一对应。
例题 • 1. 词法分析的输出由记号的种类和属性 两部分组成。 • 2. 指出NFA与DFA的主要区别。 • 正规式0*(10*1)*0*所表示的语言是含有偶数个1的0、1串 。 • 词法分析是编译的第一阶段,其主要任务是读取输入字符流(源程序),产生用于进行语法分析的的记号序列,同时过滤空白符号和注释。
例题 5.有正规式r=(a|b)*(aa|bb)(a|b)*,试给出: (1)r的正规集; (2)识别该正规集的DFA(要有步骤); (3)最小化的DFA。 解: (1)L(r)是至少含有两个相连的a或两个相连的b的a、b串的集合。
例题 5.有正规式r=(a|b)*(aa|bb)(a|b)*,试给出: (2)识别该正规集的DFA(要有步骤); 解:(2)构造r的NFA如图1所示。 确定化:(NFA中没有ε状态转移,故仅需求smove) 初态:A={0} m(A,a)={0,1} B m(A,b)={0,2} C m(B,a)={0,1,3}D m(B,b)={0,2} C m(C,a)={0,1} B m(C,b)={0,2,3}E m(D,a)={0,1,3}D m(D,b)={0,2,3}E m(E,a)={0,1,3}D m(E,b)={0,2,3}E
例题 构造(a|b)*(aa|bb)(a|b)*的NFA如图1所示。 确定化:初态为A={0} m(A,a)={0,1} B m(A,b)={0,2} C m(B,a)={0,1,3}D m(B,b)={0,2} C m(C,a)={0,1} B m(C,b)={0,2,3}E m(D,a)={0,1,3}D m(D,b)={0,2,3}E m(E,a)={0,1,3}D m(E,b)={0,2,3}E 得DFA如图2所示。 图2 图1
例题 5.有正规式r=(a|b)*(aa|bb)(a|b)*,试给出: (3)最小化的DFA。 解:(3)初始划分:{ABC,DE} 因为B状态经a状态转移与AC不在同一组,故B被分出,形成新的划分:{AC,B,DE} 又因m(A,b)=C和m(C,b)=E不在同一组,所以最终划分:{A,B,C,DE} 得最小DFA如图3。 图3 图2
a b a b {0} A A A B {0} A {0,1} B {0,1} B B A C {0} A {0,1,2} C C {0,1,2} C D C {0,2} D {0,1,2} C D D C {0,2} D {0,2} D {0,1,2} C 例:已知一个NFA如图。 (a) 用自然语言简要叙述该自动机所识别的语言 的特点,列举两个它可识别的串。 (b) 写出与该自动机等价的正规式r。 (c) 用子集法构造识别r的最小DFA。 解: (a) 语言特点是包含子串“bb”的ab符号串。如abba、abbbab。 (b) r =(a|b)*bb(a|b)* (c) 关键步骤:确定化、最小化 1.确定化:子集法构造DFA(用状态矩阵表示) 得到DFA如右(其中,C和D中含NFA的终态,故C和D是DFA的终态)
a a b b A A A A B B B B A A C C C C D C C C D D C DFA NFA • 2.最小化DFA:(利用可区分概念) • 原始划分(两个组):1={A,B} 2={C,D} • 考察所有状态转移: • move(A,a) = A, move(B,a) = B • move(A,b) = B, move(B,b) = C • move(C,a) = D, move(D,a) = C • move(C,b) = D, move(D,b) = C • 根据可区分概念将1分割成两个组1和3,得到: • 1={A} 2={C,D} 3={B} • 由于状态C和D不可区分,因此可将C、D合并为一个状态且选C作代表,得到最小DFA如右。 • 对应的图形表示如下。
第3章 语法分析 要点: <1> 什么是产生式?什么是上下文无关文法(CFG)? <2> 什么是文法的二义性和语言的二义性? 消除文法二义性的方法有哪些? <3> 什么是推导?最左推导?规范推导(最右推导)? <4> 什么是语法树?什么是分析树? <5> 自上而下语法分析与自下而上语法分析? <6> 如何消除左递归和直接左递归?如何提取左因子? <7> 如何计算FIRST和FOLLOW集? <8> 什么是递归下降分析?
第3章 语法分析 要点: <8> 如何构造预测分析表并进行预测分析? <9> 什么是短语、直接短语、句柄? <10> 如何构造SLR(1)分析表并进行“移进-归约”分析? <11> 什么是“移进-归约”和“归约-归约”冲突? <12> 为什么要进行语法分析? <13> 如何根据语言描述编写语法产生式? <14> 为什么自上而下分析不能有公共左因子和左递归? <15> 识别语言的自动机与文法的对应关系?
疑难点 • 为什么要进行语法分析? 语法分析是对程序文本(实际上是经过词法分析器处理过的,只保留了符号的类型的程序文本)进行合法性分析,其作用在于保证传递给语义分析器的程序符合编译器定义的编程语言的语法正确性要求,使语义分析器能够正确进行语义分析。
疑难点 • 自上而下分析和自下而上分析方式在分析的过程上有什么区别和联系? 自上而下分析过程实际上是一个“先展开,再匹配”的过程。它总是从开始符号开始,每一次都按照某一个产生式进行展开,并与现有的输入字符串比较。在这个展开过程中,就会产生一些可以与输入符号串的某个位置匹配的终结符,同时还会产生一些产生式中出现的非终结符。自上而下分析会反复匹配这个展开符号串中的非终结符,直到这个展开符号串中再也没有任何非终结符为止。如果这时得到的展开符号串与输入串的内容一致,则说明输入匹配成功。自上而下分析过程总是试图用某一个已知的产生式来“解释”(匹配)现在输入的状态,直到最终能够完全“解释”(匹配)输入符号串为止。
疑难点 自下而上分析过程实际上是一个“先读入,再归纳(归约)”的过程。自下而上分析过程一开始实际上程序并不知道自己将得到什么样的产生式,它只是不停地先读入字符并将其存储在一个堆栈之中,并依照某种标准(就是“移进-归约”分析表)来判断堆栈之中这些已经读入的终结符和非终结符是否可以归约成某个产生式。如果可以,它就将可以归约的部分归约成一个新的非终结符,并用它替换原来堆栈中归约出它来的那一部分(表示这一部分已经可以用这个新的非终结符代表)。如此不停分析下去,如果最终我们可以将这整个输入符号串归约成文法开始符号,就认为匹配成功。自下而上分析过程可以试图用产生式“化简”(归约)读入的符号,直到最后可以“化简”出文法开始符号为止。
疑难点 • FIRST和FOLLOW集在语法分析过程中起到的作用是什么? FIRST集合用于标记文法每一个符号(终结符和非终结符)以什么终结符开始。FOLLOW集则通常用于标记每一个非终结符(注意,没有终结符)分析结束之后紧接着的终结符能够有哪些。 FIRST集合的计算常常是自下而上计算出来的(从能右部最左端能够得到终结符的产生式开始),而FOLLOW集合则常常是自上而下计算出来的(从第一个产生式开始)。
疑难点 • 自上而下分析和自下而上分析与FIRST、FOLLOW集的关系是什么? 自上而下分析过程中实际上主要依赖FIRST集合,因为自上而下分析的基本手段是“看到一个字符来决定现在应该是哪个产生式的开始”。相反的,自下而上分析则主要依赖FOLLOW集合,因为自下而上分析的方法是现不停移进,并依照“下一个字符是什么”来决定自己移进的是不是已经能够形成一个完整的产生式。在这个过程中,FOLLOW集合就是“遇到什么终结符表示我们可以归约这个产生式”的依据。 但是,FIRST和FOLLOW集合必须一同使用,因为FOLLOW集合的计算必须建立在FIRST集合的基础之上。
疑难点 • 为什么自上而下分析不能有公共左因子和左递归? 自上而下分析不能有公共左因子的原因在于自上而下分析是依靠当前看到的第一个终结符来决定现在的格局应该按照什么产生式进行展开的。如果用于推导出同一个非终结符的不同产生式拥有相同的左因子,则自上而下分析过程就不知道应该按哪个产生式作为展开的依据,从而无法分析下去。如果使用预测分析方法分析带有公共左因子的文法,最明显的表现就在于在预测分析表中的某个表格中会出现必须填写多个产生式的情况,
疑难点 • 为什么自上而下分析不能有公共左因子和左递归? 自上而下分析不能有左递归的原因则在于:自上而下分析总是按照从左到右的方式分析输入的。因此,自上而下分析的方式总是要求每一次展开都能“消去”产生式最左端的一部分终结符,只有这样才能保证每一次展开都能够使剩下未匹配的部分变“短”一些,最终做到完全匹配。如果产生式最左端出现左递归,则每一次展开的最左边都不可能产生可以消去的终结符,那么程序将永远无法继续分析下去。值得注意的是,所谓的左递归不但包括可以看到的直接左递归,还包括经过推导能够从文法中推导出来的间接左递归。
疑难点 • 为什么自下而上分析会出现“移进-归约”和“归约-归约”冲突? 因为自下而上分析总是根据下一个终结符归属于哪一个非终结符的FOLLOW集合来确定已经移进的符号将按照哪个产生式归约。如果某一个终结符同时属于两个非终结符的FOLLOW集合,则分析器就无法确定应该按照哪个非终结符进行归约。这时就会产生“归约-归约”冲突。
疑难点 • 为什么自下而上分析会出现“移进-归约”和“归约-归约”冲突? “移进-归约”冲突的产生则是由于LR(0)项目集合中可能出现这样的一种情况: A → X. B → X.tY 其中,t是终结符,A、B、X、Y是非终结符,且t是A的FOLLOW集合中的一个成员。 在以上情形之下:产生式A的右部正好是产生式B的右部的前缀,而且B中在与A相同的前缀后紧跟的又是FOLLOW(A)中的终结符。当产生式归约了X后,如果输入串的下一个字符是t,则分析器就无法确定应该是归约还是移进,因为两种操作按照文法来看都是合理的。但如果t没有在FOLLOW(A)中出现,则不构成冲突。
例题 • 对单词的识别,是依据词法(构词)规则进行的,对句子的识别,是依据 语法 规则进行的。 • 2型文法是 上下文无关文法 ,对应的分析器是下推自动机;3型文法是正规文法,对应的分析器是有限 自动机。 • 文法的终结符集和非终结符集 的交集一定为 空 。词法分析器交给语法分析器的文法符号一定是 终结符 ,它只能出现在产生式的 右 部。 • LL(1)分析法中,第一个L的含义是自左向右扫描输入,第二个L的含义是最左推导 ,1的含义是确定下一个动作向前看1个终结符 。
例题 • 最右推导(或规范推导)是与规范归约(最左归约)互逆的一个过程,规范归约每次归约的符号串称为 句柄。 • 自上而下分析的一般方法是:对于任何一个输入序列,从文法开始符号开始,进行最左推导,反复用产生式右部的文法符号序列替换句型中的 非终结符,最终得到一个句子(终结符序列)。 • 用LR方法实现语法分析时,典型的操作有 移进 、归约、接受和报错。 • 一个文法产生的句子的集合称为该文法产生的语言。 • 递归下降分析法是一种自上而下的语法分析方法。
例题 • 在自上而下的语法分析方法中,应对文法实施以下改造: 消除左递归 以避免分析陷入死循环,提取公共左因子以避免回溯。 • 给定文法A→bA|cc,下面的符号串中,为该文法句子的是A 。 • A. cc B. bcbc C. bccbcc D. ccb • 3.已知文法G:S→A0 A→A0|S1|1。与G等价的正规式是C 。 • A. (0|1)*0 B. 0*|1*0 • C.1(0|01)*0 D. (10|01)*0
例题 • 4. 设有文法G:S→aBc|bAB, A→aAb|b, B→b|ε。 • <1> 计算非终结符S、A、B的FIRST和FOLLOW集合; • <2> 构造G的LL(1)分析表; • <3> 分析输入序列baabbb(以格局的形式写出具体的分析步骤)。 • 5. 设有文法G:S→S(S), S→ε。 • <1> 构造识别G的活前缀的DFA; • <2> G是LR(0)的吗?请说明理由; • <3> G是SLR(1)的吗? 若是构造出它的SLR分析表;若不是请说明理由。
4. 设有文法G:S→aBc|bAB, A→aAb|b, B→b|ε。 • <1> 计算非终结符S、A、B的FIRST和FOLLOW集合; • <2> 构造G的LL(1)分析表; • <3> 分析输入序列baabbb(以格局的形式写出具体的分析步骤)。 解:<1> 计算FIRST和FOLLOW: FIRST(B)={b,ε} FIRST(A)={a, b} FIRST(S)={a, b} FOLLOW(B)={c,#} FOLLOW(A)={b,#} FOLLOW(S)={#} <2> 构造预测分析表:
例题 • 5. 设有文法G:S→S(S), S→ε。 • <1> 构造识别G的活前缀的DFA; • <2> G是LR(0)的吗?请说明理由; • <3> G是SLR(1)的吗? 若是构造出它的SLR分析表;若不是请说明理由。 解:<1> 构造识别G的活前缀的DFA:
例题 解:<1> 构造识别G的活前缀的DFA: <2> G是LR(0)的吗?请说明理由; 此文法不是LR(0)文法,因为在I1、I2中有移进/归约冲突(如果仅考虑终结符,应该没有移进/归约冲突);
<3> G是SLR(1)的吗? 若是构造出它的SLR分析表;若不是请说明理由。 解:计算FOLLOW(S)={(, ), #},∵{(, ), #}∩{S}=Φ ∴ I1、I2中的移进/归约冲突可通过简单向前看一个符号解决,此文法是SLR(1)文法,它的分析表如下:
第4章 语法制导翻译生成中间代码 要点: <1> 程序的语法和语义,语义信息用文法符号的属性。 <2> 什么是语法制导翻译? <3> 为什么生成中间代码?常见中间代码的形式有哪些? <4> 符号表的作用和内容? <5> 声明语句的翻译 <6> 可执行语句的翻译
第4章 语法制导翻译生成中间代码 • 要点: • <5> 声明语句的翻译 • 定义与声明: • 类型定义与变量声明,过程定义与声明 • 变量声明:填写符号表 • 过程声明: • 左值和右值 • 四种过程调用时参数传递方式 • 名字的作用域:静态作用域原则和最近嵌套原则 • 声明中作用域信息的保存
第4章 语法制导翻译生成中间代码 要点: <6> 可执行语句的翻译 算术表达式和赋值语句的翻译 数组元素引用 布尔表达式短路计算方式的翻译: 真出口与假出口、真值链与假值链 控制语句的翻译:拉链-回填
疑难点 • 为什么要进行语义分析? 语义分析是编译器中真正分析输入程序含义的部分。它根据一组定义好的规则(语法制导定义)检查已经由语法分析部分分析完毕的、没有语法错误的程序,并将其按照一定的规则(翻译方案)生成我们想要的结果(如果是解释器,就直接解释执行;如果是编译器,则通常将其翻译成功能等价的汇编语言或其他高级语言程序)。
疑难点 • 中间代码在分析方案中起到的作用是什么? 中间代码为语义分析程序定义了一种标准的形式,如果语义分析程序需要生成不同的目标语言代码,只要更改中间代码到目标代码翻译模块就可以达到目的,语义分析部分可以不必改动。
疑难点 • 符号表在语法制导翻译过程中的作用是什么? 符号表存储了程序中所有变量、常量或过程的名字(标识符)。在最终生成的代码中,这些名字都将被转换成对应的地址(变量和常量是存储地址,而过程则是入口地址),而这一部分工作主要就是在语法制导翻译的过程中完成的,符号表在这一过程中的作用有两个:1、允许翻译程序快速地检索某一个标识符对应数据或过程的信息;2、提供一个正确的作用域管理机制,使得翻译程序能够正确访问到它当前应该访问到的地址(例如,重名的局部变量和全局变量)。
疑难点 • 为什么需要进行计算数组下标映射 ? 数组就是一块用于存储数据的固定大小的内存。由于现代计算机内部的内存都是按照一维线性结构排列的,所以现代的计算机实际上无法实现真正意义上的多维数组,必须使用一维数组来模拟。如果程序需要提供多维数组的机制,就必须设法把多维数组的多维下标映射成为一维的下标,才能做到正常访问。
疑难点 • “拉链-回填”分析方法的作用是什么?用在何处? “拉链-回填”主要用于这样一种情况:在生成代码的过程中,有时需要生成一些跳转到另一个过程首地址的程序语句,但是这个目的地地址必须在后面的代码中生成,当前还没有确定。于是我们必须将要填入这个还未生成的地址的位置记录下来,留待当这个目的地地址生成之后再回头补上,就是所谓的“回填”。如果这个跳转地址在它定义之前被引用的次数有许多次,那么这些引用的位置通常用一个链表的形式记录下来(因为不能确定这个地址要被引用多少次,用链表处理比较方便),这就是所谓的“拉链”。
例题 • 中间代码的主要形式有后缀式、三地址码和树(或图)。 • 与后缀式abc*+-d/对应的表达式是-(a+b*c)/d 。 • 中间代码的特点是什么?采用中间代码有什么好处? • 答:中间代码的特点是与具体机器(指令系统)无关;采用中间代码可以明确区分前端与后端;便于优化和移植。
例题 • 语义错误可分为静态语义错误和动态语义错误,“运算符与运算对象的类型不一致”属于静态语义错误,“无穷递归”属于动态语义错误。 • 过程(或函数)调用中, 引用 调用传递给形参的是实参的左值, 值 调用传递给形参的是实参的右值,复写/恢复调用传递给形参的是实参的 右 值。
例题 • 数组元素的地址计算公式由两部分组成,一部分是不变部分,它在 编译 时确定;另一部分是可变部分,它在 运行 时确定。 • 为数组声明a:array[1..3, 2..4]中a分配的存储空间的首地址为base_a,且每个数组元素占据一个存储单元。若以行为主存放,数组元素a[3, 3]在存储空间中相对base_a的偏移量是7 ;若以列为主存放,数组元素a[3, 3]在存储空间中相对base_a的偏移量是5 。