编译原理

龙书8章学习总结,略过了一些算法前端

第一章引论和第二章简单的语法制导翻译器

一个源程序到目标程序

源程序经过预处理器处理到编译器造成目标汇编程序到汇编器造成可重定位机器代码经过连接器/加载器变成目标机器代码程序员

预处理器:展开全部宏定义,处理含有#部分的代码,还有删除全部的注释//和/* */。算法

编译:进行代码的优化还有符号的汇总(符号表)以及语义分析 语法分析 词义分析及优化后生成相应的汇编代码文件。编程

汇编:将指令转成当地操做系统的机器码;生成可重定向的目标文件:x86体系下的.obj 和unix下的.o文件后端

连接过程:数组

将第一个步骤所生成的.o文件里的段整合在一块儿,进行【符号解析】,符号解析指的是将每一个符号引用恰好和一个符号定义联系起来。对符号表里的未定义的符号找到其定义的地方,代码段中,对全部指令未有其定义的符号都将其变为正确的地址例如extern行的代码以及main函数中的printf。解析正确后,再进行【符号重定位】,对其分配相应的虚拟地址。全部符号都拥有其正确的虚拟地址后,而后生成最终可执行的.exe文件,其运行的时候只须要代码段和数据段。数据结构

编译器的结构

分析

分析代码的语法语义的构成,收集有关信息放入符号表,前端闭包

综合

根据中间表示和符号表中的信息构造目标程序,后端函数

步骤

词法分析

会根据字符流生成词素序列,并将有关信息放入符号表条目学习

2.语法分析

构造相应的语法数

3.语义分析

根据语法树和符号表中的信息来检查源程序是否和语言定义的语义一致也会收集类型信息以便中间代码生成

4.中间代码

生成一种可以被轻松翻译成为目标机器上的语言

5.代码优化

机器无关的代码优化改进中间代码,以便生成更好的目标代码

6.代码生成

以源程序的中间表示形式做为输入映射到目标语言

7.符号表管理

记录源程序中使用的变量的名字,并收集和每一个名字的各类属性相关的信息

8.将多个步骤组合成趟

前端步骤1-4位一趟,代码优化为可选趟,代码生成为后端趟

编译器的前端

源程序 -> 词法分析器-> 词法单元-> 语法分析器 -> 语法分析树-> 中间代码生成器-> 三地址代码

词法分析器、语法分析器、中间代码生成器都依赖于符号表

词法分析器使得翻译器能够处理由多个字符组成的构造,好比标识符。标识符由多个字符组成,在语法分析阶段看成单元称为词法单元。

语法分析器会生成抽象语法树,它表示了源程序的层次化语法结构

中间代码生成器会将语法树进一步翻译为三地址代码吗如,x = y op z

第三章词法分析

词法分析是编译的第一阶段。主要任务是读入源程序的输入字符、将它们组成词素,生成并输出一个词法单元序列,每一个词法单元对应于一个词素。词法单元序列被输出到语法分析器进行语法分析。词法分析器还要和符号表进行交互。当词法分析器发现了一个标识符词素时,它要将这个词素添加到符号表中

除了识别词素以外的其余任务之一是过滤掉源程序中的注释和空白(空格、换行符、制表符以及在输入中用于分隔词法单元的其余字符)另外一个任务是将编译器生成的错误消息与源程序的位置联系起来。

相关术语

词法单元

一个词法单元名和一个可选的属性值组成。理解为对一个单词也就是词素的描述,也能够称之为总称。好比全部数字的词法单元都叫number,<=,!=等叫comparison,else就是else

模式

描述了一个词法单元的词素可能具备的形式

词素

源程序中国的一个字符序列

词法单元的规约

经过必定的正则定义匹配相应的模式识别标识符关键字保留字,消除空白符

状态转换图

状态转换图有一组被称为”状态“的结点。词法分析器扫描输入串的过程当中寻找和某个模式的匹配的词素,而转换图中的每一个状态表明一个可能在这个过程当中出现的状况。

处理保留字和标识符:

初始化时就将各个保留字填入符号表中

为每一个关键字创建单独的状态转换图

有穷自动机

不肯定的有穷自动机:对其边上的标号没有任何限制,一个符号标记离开同一状态的多条边

肯定的有穷自动机:有且只有一条离开该状态、以该符号为标号的边

任务

剔除空白和注释

容许词法单元之间出现任意数量的空白,还有程序中会出现注释当词法分析器消除了,语法分析器就不须要再考虑了

预读

在读取字符流的时候须要判断下一个字符是否是与当前字符须要组合,好比读了>还须要读下一个是不是=构成>=

常量

表达式中出现的整形常量能够建立一个表明整形常量的终结符号入<NUM,31>元组表明有整型常量31

识别关键字和标识符

程序设计语言中都有它相应的固定字符串组成的关键字,字符串还能够标识符来做为变量、数组、函数等命名会将其当作终结符处理

第四章语法分析

语法分析器从词法分析器得到一个由词法单元组成的串,并验证这个串能够由源语言的文法生成

上下文无关法

一个有穷的非终结符(或变元)的集合;一个有穷的终结符的集合;一个有穷的产生式集合;一个起始非终结符(变元)。

不去判断做用域等问题造成一个AST

递归降低算法

递归降低算法其实很简单,它的基本思路就是按照语法规则去匹配 Token 串。

对于一个非终结符,要从左到右依次匹配其产生式中的每一个项,包括非终结符和终结符。在匹配产生式右边的非终结符时,要降低一层,继续匹配该非终结符的产生式。若是一个语法规则有多个可选的产生式,那么只要有一个产生式匹配成功就行。若是一个产生式匹配不成功,那就回退回来,尝试另外一个产生式。

程序可能遇到不一样层次的错误

词法错误

包括标识符、关键字或运算符拼写错误和没有在字符串文本上正确地加上引号

语法错误

包括分号放错位置、花括号多余或缺失。

语义错误

包括运算符合运算份量之间的类型不匹配。例如void某个方法中返回某个值的return语句。

逻辑错误

能够是因程序员的错误推理而引发的任何错误

语法分析方法检测语法错误

错误恢复策略
恐慌模式的恢复

语法分析器一旦发现错误不断丢弃输入中的符号,一次丢弃一个符号,直到找到同步词法单元集合中的某个元素位置好比分号或者}

短语层次的恢复

当发现一个错误时,语法分析器能够在余下的输入上进行局部性纠正。简单来讲就是对一个输入串进行增删改。

错误产生式

经过预测可能遇到的常见错误,咱们能够在当前语言的文法中加入特殊的产生式。

全局纠正

在理想状态下,咱们但愿编译器在处理一个错误输入串时经过最少的改动将其转化为语法正确的串。

第五章语法制导的翻译

在语法分析的同时进行语义翻译叫作语法制导翻译。经过构造出的语法分析树,而后经过访问这棵树的各个结点来计算结点的属性值。

语法制导定义

是一个上下文无关法和属性及规则的结合。属性和文法符号相关联,规则和产生式相关联。

继承属性和综合属性

其实就是属性计算。须要判断AST中节点中所需的类型。经过子节点计算出来的叫S属性也就是综合属性。经过父亲节点或者兄弟节点计算出来的叫作I属性继承到的属性

SDD依赖图

依赖图描述了某个语法分析数中的属性实例之间的信息流。从一个属性实例到另外一个实例的边表示计算第二个属性实例时须要第一个属性实例的值。

也就是一个表达式的值是否须要另一个表达式求出来的值,定义了属性的求值顺序

非书中:

语义分析
控制流检查

像 return、break 和 continue 等语句,都与程序的控制流有关,它们必须符合控制流方面的规则。在 Java 这样的语言中,语义规则会规定:若是返回值不是 void,那么在退出函数体以前,必定要执行一个 return 语句,那么就要检查全部的控制流分支,是否都以 return 语句结尾。

闭包分析

不少语言都支持闭包。而要正确地使用闭包,就必须在编译期知道哪些变量是自由变量。这里的自由变量是指在本函数外面定义的变量,但被这个函数中的代码所使用。

引用消解

在高级语言里,咱们会作变量、函数(或方法)和类型的声明,而后在其余地方使用它们。这个时候,咱们要找到定义和使用之间的正确引用关系。

类型分析和处理

在计算机语言里,类型是数据的一个属性,它的做用是来告诉编译器或解释器,程序能够如何使用这些数据。

也就是上述的属性计算(综合属性和继承属性)

第六章中间代码生成

许多方法均可以用于中间表示。包括抽象语法树和三地址码。在给定的源语言的一个程序翻译成特定的目标机器代码的过程当中,一个编译器构造出一系列中间表示。

DAG

表达式的有向无环图,是中间代码在内存中其中一种数据结构。将本来的AST树形结构中消除了冗余的子树,就是其中有可能一个变量被屡次使用,消除多余的这个变量子树合成一个。

三地址代码

在三地址代码中,一条指令的右侧最多有一个运算符。三地址码是一棵语法树或一个DAG的线性表示形式。

三地址码基于两个基本概念:地址和指令。

地址能够具备以下形式:

名字。源程序名字做为三地址代码中的地址,实现中源程序名字会被替换成指定符号表条目的指针。

常量。

编译器生成的临时变量。

静态单赋值形式SSA

全部的赋值都是针对具备不一样名字的变量的

SSA中有一种函数表示将一个变量不一样的取值合并起来,根据上文的条件来获得其中的取值

类型的检查和翻译

类型检查

保证运算份量的类型和运算符的预期类型相匹配

翻译时的应用

根据一个名字的类型,编译器能够肯定这个名字在运行时刻须要多大的存储空间

第七章运行时刻环境

编译器建立并管理一个运行时刻环境,它编译获得的目标程序就运行在这个环境中。为源程序中命名的对象分配和安排存储位置,肯定目标程序访问变量时使用的机制,过程间的链接,参数传递机制,以及与操做系统、输入输出设备及其余程序的接口。

存储组织

从编译器编写者的角度来看,正在执行的目标程序在它本身的逻辑空间内运行,其中每一个程序值都在这个空间中有一个地址。对这个逻辑地址空间的管理和组织是由编译器、操做系统和目标机共同完成的。

静态和动态存储分配

静态属于编译时刻,动态属于运行时刻

栈式分配。一个方法的局部名字在栈中分配空间。

堆存储。有些数据的生命周期要比创造它的某次方法调用更长,这些数据一般被分配在一个可复用存储的”堆“中。

空间的栈式分配

有些语言使用过程、函数或方法做为用户自定义动做的单元,几乎全部针对这些语言的编译器都把它们的运行时刻存储按照一个栈进行管理。方法被调用时用于存放该方法的局部变量空间被压入栈。当方法调用结束,这个空间被弹出栈。

堆管理

它被用来存储那些生命周期不肯定,或者将生存到被程序显示删除为止的数据。

存储管理器

分配

当程序为一个变量或对象请求内存时,存储管管理器产生一段连续的具备被请求大小的堆空间。

回收

存储管理器把被回收的空间返还到空闲空间的缓冲池中,这样它能够复用该空间来知足其余的分配请求。

第八章代码生成

编译器的最后一个步骤就是代码生成器。以编译器前端生成的中间表示和相关符号表信息做为输入,输出语义等价的目标程序。

目标程序必须保持源程序的语义含义,还必须具备很高的质量。它必须有效地利用目标机器上的可用资源。

代码生成器三个主要任务:指令选择、寄存器分配和指派、以及指令排序。

指令选择

代码生成器必须把IR程序映射成为能够在目标机上运行的代码序列。完成这个映射的复杂性由以下的因素决定:

IR的层次、指令集体系结构自己的特性、想要达到的生成代码的质量。

寄存器分配

代码生成的关键问题之一是决定哪一个值放在哪一个寄存器里面。寄存器是目标机上运行速度最快的单元,可是咱们一般没有足够的寄存器来存放全部的值。

求值顺序

计算执行的顺序会影响目标代码的效率。

目标代码优化

指令排序

由于有指令级别的并行因此有的时候指令重排有助于代码流水线执行的时候,其中有多条代码能够同时运行

窥孔优化

提供一个固定大小的窗口,并检查窗口内的指令,看看是否能够优化。

相关文章
相关标签/搜索