低级语言
高级语言
源程序:用汇编语言或高级语言编写的程序称为源程序
目标程序:用目标语言所表示的程序
目标语言:可以是介于源语言和机器语言之间的中间语言,可以是某种机器的机器语言,也可以是某种机器的汇编语言
翻译程序:将源程序转换为目标程序的程序称为翻译程序,它是指各种语言的翻译器,包括汇编程序和编译程序,是汇编程序、编译程序以及各种变换程序的总称
源程序是翻译程序的输入,目标程序是翻译程序的输出。
编译的五个基本阶段:词法分析、语法分析、语义分析&生成中间代码、代码优化、生成目标程序
词法分析
任务:分析和识别单词
源程序是由字符序列构成的,词法分析扫描源程序(字符串),根据语言的词法规则分析并识别单词,并以某种编码形式输出
语法分析
任务:根据语法规则(即语言的文法),分析并识别出各种语法成分,如表达式、各种说明、各种语句、过程、函数、程序等,并进行语法正确性检查
语义分析、生成中间代码
任务:对识别出的各种语法程序进行语义分析、并产生相应的中间代码
生成中间代码的目的:1.便于做优化处理;2.便于编译程序的移植
代码优化
任务:为了得到高质量的目标程序
生成目标程序
整个编译的过程中都要建表查表以及错误处理,所以编译程序中都要包括表格管理和出错处理两部分
遍:对源程序(包括源程序中间形式)从头到尾扫描一次,并做有关的加工处理,生成新的源程序中间形式或目标程序,通常称之为一遍
通常将与源程序有关的编译部分称为前端,包括词法分析、语法分析、语义部分、中间代码生成、代码优化等分析部分;将与目标机有关的部分称为后端,包括目标程序生成,也叫综合部分
字母表:符号的非空有限集
符号:字母表中的元素
符号串:符号的有穷序列
空符号串:无任何符号的符号串(ε)
最右推导又称为一般推导、规范推导
文法的分类:0(短语结构文法)、1(上下文有关文法)、2(上下文无关文法,与BNF表示相等价,可以由下推自动机接受)、3(正则文法,可以由有穷自动机接受)型文法
非形式化、形式化解释
文法四元式:V_n(非终结符号集)、V_t(终结符号集)、P(产生式或规则的集合)、Z(开始符号,又称为识别符号, Z ∈ V n Z\in V_n Z∈Vn)
语言:文法产生的所有句子的集合
句子:由终结符组成的字符串(有非终结符的叫句型),句子是特殊的句型
短语:可以大致理解为句子
句柄:最左简单短语
递归文法可用又穷条规则定义无穷语言
左递归文法不能使用自顶向下的方法进行语法分析
语法树:句子结构的图示表示法,它是一种有向图,由结点和有向边组成。
节点:符号,根节点:识别符号,中间节点:非终结符,叶节点:终结符或非终结符,有向边:表示节点间的派生关系
对句型中最左简单短语(句柄)进行的规约称为规范规约,规范规约与规范推导互为逆过程,通过规范规约或规范推导得到的句型称为规范句型
若对于一个文法的某一句子存在两棵不同的语法树或两个不同的规范推导,则该文法是二义性文法,否则是无二义性文法。
若一个文法的某规范句型的句柄不唯一,则该文法 是二义性的,否则是无二义性的。
若文法中有如 U : : = U U::=U U::=U的规则,则这就是有害规则,它会引 起二义性。
多余规则
若某文法中无有害规则或多余规则,则称该文法是压缩过的。
词法分析的功能:根据词法规则识别及组合单词,进行词法检查;对数字常数完成字符串到二进制数值的转换;删去空格字符和注解
单词的种类:保留字、标识符、常数、分界符(单分界符、双分界符)
常用的单词内部形式:
词法规则->状态图->词法分析程序
一个确定的有穷自动机(DFA)M是一个五元式: M = ( S , ∑ , δ , s 0 , Z ) M=(S, \sum ,\delta ,s_0 ,Z) M=(S,∑,δ,s0,Z)
若 δ \delta δ是一个多值函数,且输入可允许为ε,则有穷自动机是不确定的,即在某个状态下,对于某个输入字符存在多个后继状态,称为非确定的有穷自动机(NFA)
子集法将nfa变为dfa
lex二义性问题的两条原则:最长匹配原则、最优匹配原则
功能:根据文法规则,从源程序的单词符号串中识别出语法成分,并进行语法检查
自顶向下分析法
问题
左递归(改右递归)
回溯
方法
递归下降子程序法
LL分析法(下推自动机)
自底向上分析法
问题
句柄的识别问题
若两个以上规则的右部有相同的符号串且构成句柄,选哪个候选式?
方法
算符优先分析法
LR分析法
自顶向下分析方法的特点
消除左递归的方法
使用扩充的BNF表示来改写文法
eg. E : : = E + T ∣ T E::=E+T|T E::=E+T∣T -> E : : = T + T E::=T{+T} E::=T+T
将左递归规则改为右递归规则
避免回溯
要求文法 F I R S T ( α i ) ⋂ F I R S T ( α j ) = ∅ ( i ̸ = j ) FIRST(\alpha_i) \bigcap FIRST(\alpha_j) = \varnothing (i\not=j) FIRST(αi)⋂FIRST(αj)=∅(i̸=j)
消除回溯的途径
改写文法
对具有多个右部的规则反复提取左因子
超前扫描
当文法不满足避免回溯的条件时,即各选择的首符 号相交时,可以采用超前扫描的方法,即向前侦察各输 入符号串的第二个、第三个符号来确定要选择的目标。
这种方法是通过向前多看几个符号来确定所选择 的目标,从本质上来讲也有回溯的味道,因此比第一 种方法费时,但是读的仅仅是向前侦察情况,不作任 何语义处理工作。
为了在不采取超前扫描的前提下实现不带回溯的自顶向
下分析,对文法需要满足两个条件:
递归子程序法(递归下降分析法):对语法的每一个非终结符都编一个分析程序,当根据文法和当时的输入符号预测到要用某个非终结符去匹配字符串时,就调用该非终结符的分析程序。对应的是最左推导的过程
LL分析法
此过程由分析表、执行程序(总控程序)、符号栈(分析站)三部分组成
基本算法思想:从输入符号串开始,通过反复查找当前句型的句柄
(最左简单短语),并利用有关规则进行规约。若能规约 为文法的识别符号,则表示分析成功,输入符号串是文法的合法句子;否则有语法错误。
移进-规约分析:设置符号栈,用来纪录分析的历史和现状,并根据所面临的状态,确定下一步动作是移进还是规约。
需要注意的是:1. 栈内符号串+未处理符号串=当前句型;2. 句柄都在栈顶
实际上并未真正解决句柄的识别问题
算符优先分析
出错情况:
上述过程不一定是严格的最左规约(规范规约),但可以分析二义性文法所产生的语言(二义性文法若按规范分析,其句柄不唯一)
若文法中无形如 U ∷ = . . . V W . . . U∷= ...VW... U∷=...VW...的规则,这里 V , W ∈ V n V, W\in Vn V,W∈Vn则称G为OG文法,也就是算符文法(算符文法不允许两个非终结符相邻)
算符优先文法(OPG—Operator Precedence Grammar)的定义:设有一OG文法,如果在任意两个终结符之间,至多只有 上述关系中的一种,则称该文法为算符优先文法(OPG)。
素短语:文法G的句型的素短语是一个短语, 它至少包含有一个终结符号,并且除它自身以外不再 包含其它素短语。
LR分析法
从左到右扫描(L)自底向上进行规约(R)(是规范规约,也即最右推导),是自底向上分析方法的高度概括和集中
优点:适合文法类足够大,适用于所有上下文无关文法,分析效率高,报错及时,可以自动生成
缺点:手工实现工作量大
状态栈:放置分析器状态和文法符号
分析表:由两个矩阵组成,其功能室指示分析器的动作,是移进还是规约,根据不同的文法类要采用不同的构造方法
控制程序:执行分析表所规定的动作,对栈进行操作
由分析过程可以看到:
LR文法
LR(0)文法
(规范) LR(1)分析法
若项目集 [ A → α ⋅ B β ] [ A \rightarrow \alpha \cdot B \beta ] [A→α⋅Bβ]属于 I I I时,则 [ B → ⋅ γ ] [ \mathbf { B } \rightarrow \cdot \gamma ] [B→⋅γ]也属于I。
把 F I R S T ( β ) FIRST(\beta) FIRST(β)作为用产生式归约的搜索符(称为向前搜索符),即用产生式$ \mathbf { B } \rightarrow \gamma $归约时查看的符号集合(用以代替 SLR(1)分析中的FOLLOW集),并把此搜索符号的集合也 放在相应项目的后面,这种处理方法即为LR(1)分析方法。
LR(1)分析法的特点:
LALR(1)分析
LR分析法总结
适用文法范围:
L R ( 0 ) ⊂ S L R ( 1 ) ⊂ L R ( 1 ) ⊂ 无 二 义 文 法 \mathbf { L } \mathbf { R } ( \mathbf { 0 } ) \subset \mathbf { S } \mathbf { L } \mathbf { R } ( \mathbf { 1 } ) \subset \mathbf { L } \mathbf { R } ( \mathbf { 1 } ) \subset 无二义文法 LR(0)⊂SLR(1)⊂LR(1)⊂无二义文法
分析表大小:
LR(0) 和SLR(1) 较小,规范 LR(1)最大,LALR(1)适中。
报错效率: LALR(1)会迟报(但不会漏报)
翻译的任务是将中缀表达式转换为逆波兰表示
输入文法:未插入动作符号时的文法,由输入文法可以通过推导产生输入序列
翻译文法:插入动作符号的文法,翻译文法可以通过推导产生活动序列(输入序列&动作序列),是上下文无关文法,终结符号级有输入符号和动作符号组成
属性分为综合属性和继承属性
@set_table:将声明的变量填入符号表(语义)
L-属性翻译文法是带有下列说明的翻译文法:
文法中的终结符,非终结符及动作符号都带有属 性,且每个属性都有一个值域。
非终结符及动作符号的属性可分为继承属性和综 合属性。
开始符号的继承属性具有指定的初始值。
输入符号(终结符号)的每个综合属性具有指定
的初始值。
属性值的求值规则如下:
继承属性——体现自顶向下,自左向右的求值特性。
综合属性——体现自底向上,自右向左的求值特性。
在翻译文法的基础上可设计其属性翻译文法,以便语义分析过程中生成完整的四元式,完成翻译。
输入文法、翻译文法(输入文法+动作符号)、属性翻译文法
在编译过程中,编译程序用来记录源程序中各种名字的特性信息,所以也称为名字特性表。
名字: 程序名、过程名、函数名、用户定义类型名、变量名、常量名、枚举值名、标号名等。
特性信息: 上述名字的种类、类型、维数、参数个数 及目标地址(存储单元地址)等。
建表和查表的必要性(符号表在编译过程中的作用):
源程序中变量要先声明,然后才能引用。
用户通过声明语句,声明各种名字,并给出它们的 类型维数等信息。编译程序在遇到这些声明语句时,应 该将声明中的名字以及信息登录到符号表中,同时编译 程序还要给变量分配存储单元。
存储单元地址也必须登录在符号表中。
当编译程序编译到引用所声明的变量时(赋值或引 用其值),要进行语法语义正确性检查(类型是否符合 要求等)和生成相应的目标程序,这就需要查符号表以 取得相关信息。
非分程序的结构语言:每个可独立进行编译的程序单元是一个不包含有子模块的单一模块。如FORTRAN语言。
基本处理办法:
子程序、函数名和公共区变量填入全局符号表
在子程序(函数)声明部分读到标识符时,构造局部符号表
查本程序单元局部符号表,有无同名
在语句部分读到标识符,查表
查本程序单元局部符号表,有无同名
程序单元结束:释放该程序单元的局部符号表
程序执行完成:释放全部符号表
符号表的组织方式
无序符号表
按扫描顺序建表,查表要逐项查找
查表操作的平均长度为 ( n + 1 ) / 2 (n + 1)/2 (n+1)/2
有序符号表
符号表按变量名进行字典式排序
线性查表: ( n + 1 ) / 2 (n + 1) / 2 (n+1)/2
折半查表: L o g 2 n − 1 \boldsymbol { L } \boldsymbol { o } \boldsymbol { g } _ { 2 } \boldsymbol { n } - \mathbf { 1 } Log2n−1
散列符号表(Hash表)
符号表地址 = Hash(标识符)
分程序的结构语言:模块内可嵌入子模块
标识符的作用域:标识符局部于所定义的模块(最小模块)
静态存储分配:在编一阶段由编译程序实现对存储空间的管理,和为源程序中的变量分配存储的方法(但并不是所有的数据空间大小都能在编译过程中确定)
动态存储分配:在目标程序运行阶段由目标程序实现对存储空间的组织与管理,和为源程序中的变量分配存储的方法(编译时要生成动态分配的目标指令)
由于每个变量所需空间的大小在编译时已知,因此可以用简单的方法给变量分配目标地址。
这种分配策略要求语言不允许指针或动态分配,不允许递归调用过程。典型的例子是Fortran77。
由于编译时还不能具体确定某些数据空间的大小,故对它们分配存储空间必须在程序运行时进行。这时,编译程序生成有关存储分配的目标代码,实际上的分配要在目标程序运行时进行。这种分配方式称为动态存储分配。
对于分程序结构,而且允许递归调用的语言,常使用栈式动态存储分配,即使用一个类似于堆栈的“运行栈” 来实现数据区的分配。
分配策略是: 整个数据区为一个堆栈
一个典型的活动记录可以分为三部分
一般编译程序都生成中间代码,然后再生成目标代码,主要优点是可移植(与具体目标程序无关),且易于目标代码优化
波兰表示法的优点:
在该表示中,每条指令由 n 个域所组成,通常第 一个域表示操作符,其余为操作数。
常用的n元表示是三元式、四元式
既然是“抽象机”,就是表示它并不是实际的物理目标机器而通常是虚拟的一台“堆栈计算机”。该堆栈式计算机主要由若干寄存器、一个保存程序指令的储存器和一个堆栈式数据及操作存储组成。
寄存器有:
正确的源程序:通过编译,生成目标代码。
错误的源程序:通过编译,发现并指出错误。
从编译角度,将错误分为两类:语法错误和语义错误
语法错误:程序结构不符合语法(包括词法)规则的错误
语义错误:程序不符合语义规则或超越具体计算机系统的 限制
错误局部化处理:指当编译程序发现错误后,尽可能将 把错误的影响限制在一个局部的范围,避免错误扩散和影响程序其它部分的分析。
上下文有关分析:即标识符的作用域
类型的一致性检查
语义处理:
声明语句:其语义是声明变量的类型等,并不要求做其他的操作。语义分析程序的工作是填符号表,登录名字的特征信息,分配存储。
执行语句:语义是要做某种操作。 语义处理的任务:按某种操作的目标结构生成中间代码或目标代码。
栈式抽象机:由三个存储器、一个指令寄存器和多个地址寄存器组成
存储器:
编译程序处理声明语句要完成的主要任务为:
对于已声明的实体,在处理对该实体的引用时要做的事情:
@insert 的功能是:
数组变量声明的处理
对于静态数组,即数组的大小在编译时是已知的,编译程 序在处理数组声明时,可建立一个数组模板(又称为数组信息 向量)以便以后的程序中引用该数组元素时,可按照该模板提 供的信息,计算数组元素(下标变量)的存储地址。
对于动态数组,其大小只有在运行时才能最后确定。我们在编译时仅为该模板分配一个空间,而模板本身的内容将在运 行时才能填入。
分析表达式的主要目的是生成计算该表达式值的代码。通 常的做法是把表达式中的操作数装载(LOAD)到操作数栈(或运行栈)栈顶单元或某个寄存器中,然后执行表达式所指定的操作,而操作的结果保留在栈顶或寄存器中。
传值(call by value) — 值调用
实现:
过程体中对形参的处理:对形参的访问等于对相应实参的访问
特点: 数据传递是单向的
传地址(call by reference) — 引用调用
实现:
过程体中对形参的处理:通过对形参的间接访问来访问相应的实参
特点:结果随时送回调用段
传名(call by name )
又称名字调用。即把实参名字传给形参。这样在过程体中引用形参时,都相当于对当时实参变量的引用。
当实参变量为下标变量时,传名和传地址调用的效果可能会完全不同。
传名参数传递方式,实现比较复杂,其目标程序运行效率较低, 现已很 少采用。
分程序索引表:块头出现的地址
分程序符号表:end出现的位置
目的:提高目标代码运行效率
原则:进行优化必须严格遵循“不能改变原有程序语义”原则
优化的分类:
从优化的层次,与机器是否有关,分为:
从优化涉及的范围,又分为:
满足以下三个条件的程序段,称为基本块: