总述
北航张莉《编译技术》笔记,看着玩玩
第一章 编译概述
1.1 什么是程序设计语言
人与计算机之间的中介,充当通信工具
1.1.1 程序设计语言的定义方法
对语言结构形式的描述:文法(grammar)或语法(syntax)
语言所表达内容的含义:语义
本书采用属性文法。属性文法是一种半形式化的语义描述方法
1.1.2 程序设计语言的处理系统
源程序(source program)、源语言(source language)、目标程序(object program)或目标代码(object code)、目标语言(object language)
1.1.3 编译程序和解释程序
编译程序也可以输出介于低级语言和高级语言之间的某种中间语言(intermediate)
中间程序可以理解为某种虚拟计算机的机器语言
解释程序(interpreter):能对编译得到的中间语言(甚至是源语言)进行解释执行的程序
解释程序可以理解为这种虚拟计算机的模拟软件
编译型语言运行时不需要重新翻译,执行效率高;但是依赖编译器,跨平台性差
常常用于开发操作系统、大型应用程序、数据库系统等
常见的编译型语言有 C/C++、Pascal/Object Pascal(Delphi)等
解释执行程序:需要对中间代码甚至源代码逐行解释,速度较慢,优点为:
- 可以运行时动态确定数据
- 中间代码的信息可以辅助调试
- 可以边修改边解释执行,缩短再次编译运行的时间
- 处理系统易开发
- 中间代码占用内存少
- 易于移植(中间语言的机器无关性)
T型图
1.2 与编译程序相关的处理系统
1.各种翻译程序
翻译是广义的翻译,开阔下思路
2.预处理器
把源程序变换为其它语言程序再进行编译。当然,与源程序的不对应性会导致程序调试困难
3.宏处理器
1.3 编译程序和程序设计环境
1.4 编译程序的构造
1.词法分析
是以识别单词为目标的简单的语法分析程序
token
的种类:
- 保留字或关键词,如
begin
、if
、for
- 标识符
- 常量,包括浮点数、整数、各进制数、字符常量、字符串常量等
- 分界符和运算符
空格和注释会被过滤
可以用枚举类具体实现
2.语法分析
根据语法规则将token组合成大的语法类或语法成分,如变量声明、表达式、语句、函数和过程等
输出:一棵语法分析树(简称语法树)
叶结点:token
非叶结点:语法成分
3.语义分析
确定源程序的意义
事实上还要同时进行语义检查,比如赋值类型不匹配等
语义动作还包括生成中间代码
4.代码生成
通常是将源程序的中间形式转换为汇编语言或者机器语言,可通过代码优化进一步优化
5.代码优化
机器无关的优化
常数表达式的计算、应用操作符的性质(结合性、可交换性、分配性等)
多次出现的相同子表达式一次性计算、将循环内不变的语句移到循环外等
机器有关的优化
- 寄存器分配等
6.符号表管理
7.错误检测与处理
以上是逻辑结构,实现时常常结合实现
8.前端和后端(front end and back end)
前端:与源语言有关而与目标机无关的部分(即分析部分)
- 词法分析、语法分析
- 符号表建立
- 语义分析、中间代码生成
- 相应部分的错误处理和符号表操作
- 与机器无关的代码优化
后端:与目标机有关的部分(即综合部分),与源语言无关而仅仅依赖于中间语言
- 目标代码生成
- 相应部分的错误处理和符号表操作
- 与机器有关的代码优化
9.遍(pass)
1.5 编译技术在软件工程中的应用
略,用处不大
练习 1
基本是概念复述
第二章 文法和语言的概念和表示
2.1 文法的非形式讨论
文法:形式上描述和规定语言结构的规则,也称语法
2.1.1 语法树
语法树结点 | 语法 | 形式语言 |
---|---|---|
带尖括号的结点 | 语法成分 | 非终结符号 |
不带尖括号的结点 | 单词符号 | 终结符号 |
2.1.2 规则
2.1.3 推导
通常把规则称为产生式,用符号 :==
或
\(\rightarrow\) 表示
用符号 \(\Rightarrow\) 表示推导
两个以上的语法成分同时存在时可任选其中之一并根据规则重复上述推导,直到推出句子
练习 2-1
2.2 符号、符号串及其集合的运算
任何一种语言都是由该语言的基本符号所组成的符号串集合
2.2.1 字母表和符号串
字母表:元素的非空有穷集合
符号:字母表中的元素
符号串:由字母表中的符号所组成的任何有穷序列
空符号串 \(\varepsilon\) :不包含任何符号的符号串
符号串的递归定义:
- $ $ 是 \(\Sigma\) 上的符号串
- 若 $ x$ 是 \(\Sigma\) 上的符号串,且 \(a \in \Sigma\) ,则 $ xa $ 或 $ ax $ 是 \(\Sigma\) 上的符号串
- $ y $ 是 \(\Sigma\) 上的符号串,当且仅当 \(y\) 可由 ① 和 ② 产生
前后拼接
通常用 \(a、b \dots\) 和 \(S、T、U \dots\) 表示符号,用 \(s、t、u\) 表示符号串,用 \(A、B、C、 \dots 、 R\) 表示符号串集合
2.2.2 符号串及其集合的运算
符号串相等:各个符号依次相等
符号串中符号的顺序很重要
符号串的运算:
符号串长度:$ |x| $,数值上等于组成该符号串的符号个数
符号串连接:若 \(x\) 和 $ y $ 是两个符号串,其连接为 $ xy $ ,特殊地,有 $ x = x = x $
集合的乘积运算:符号串集合 $ A $ 和 $ B $ 的乘积 \(AB = \{xy | x \in A, y \in B\}\) ,特殊地,有 $ {} A = A {} = A $
符号串的幂运算:
集合的幂运算:
集合 \(A\) 的闭包 \(A^*\) 和 集合 $ A $ 的正闭包 \(A^+\) :$ A^+ = A^1 A^2 A^n \ A ^* = A^0 A^*$,有 \(A^+ = AA^* = A^*A\)
2.3 文法和语言的形式定义
2.3.1 文法的形式定义
产生式或规则:有序对 $(U, x) $ ,通常写作 \(U::=x\) 或 \(U \rightarrow x\),其中 \(U\) 是符号,是产生式的左部,\(x\) 是有穷符号串,是产生式的右部
字汇表:所有的规则左部和右部中的所有符号组成的集合
非终结符号:文法 \(G\) 中作为规则左部出现的符号,也称语法成分,形成了非终结符号集 \(V_n\)
终结符号:规则中不属于 \(V_n\) 的符号,形成了终结符号集合 \(V_t\)
识别符号或开始符号:符号 \(Z\) 至少是一条规则的左部
BNF 表示法:
合并具有相同左部的规则
文法:一个四元组 \(G = <V_n, V_i, P, Z>\)
\(P\):产生式或规则的集合,\(Z\):文法的识别符号
2.3.2 推导的形式定义
直接推导:
间接推导:
2.3.3 语言的形式定义
设 $G[Z] $ 是定义在字汇表 \(V\) 上的一个文法,其中 \(Z\) 是识别符号,在此省去了尖括号
句型:如果 \(Z \stackrel{*} \Rightarrow x\),且 \(x \in V^*\),则称 $x $ 是文法 \(G\) 的一个句型
句子:如果 \(Z \stackrel{+} \Rightarrow x\),且 \(x \in V^*_t\),则称 $x $ 是文法 \(G\) 的一个句子
- 句子是语言的最小单位
- 句子是从文法的识别符号推导出来的由终结符号所组成的符号串
- 定义式是充要条件
- 若用 \(Z \stackrel{*} \Rightarrow x\),有可能 \(Z = x\),与 \(x \in V^*_t\) 矛盾
语言:文法 \(G[Z]\) 产生的所有句子的集合,记为 \(L(G[Z])\),称为文法 \(G[Z]\) 所定义的语言,即 \(L(G[Z]) = \{x | x \in V^*_t,且Z \stackrel{*} \Rightarrow x \}\)
- 语言是所有终结符号串所组成的集合的一个子集,即 \(L(G[Z]) \subseteq V^*_t\)
规范推导:最右推导
- 每个句子都有一个规范推导,但并非每个句型都有规范推导
- 可规范推导出的句型称为“规范句型”
等价文法:\(G\) 和 \(G \prime\) 是两个不同的文法,如果 \(L(G) = L(G\prime)\) ,称二者为等价文法。
- 定义的语言相同即等价
- 为了某种目的改写文法
2.3.4 递归规则与递归文法
递归规则:\(U::=xUy\)
- 若 \(x = \varepsilon\),则 \(U::=Uy\) 称为左递归规则
- 若 \(y = \varepsilon\),则 \(U::=xU\) 称为右递归规则
- 若 \(x, y \neq \varepsilon\),则 \(U::=xUy\) 称为自嵌入的规则
文法的递归性质:
- 直接递归:文法中至少包含一条递归规则,则称文法是直接递归的
- 间接递归
- 对文法中任一非终结符号,若能建立一个推导过程,在推导所得的符号串中又出现了该终结符本身,则文法是递归性的;反之是非递归性的
含义很容易理解,只是用形式语言描述似乎看起来啰嗦些
- 可以由有穷的规则刻画无穷的语言
- 左递归文法无法采用自顶向下的分析算法
2.3.5 短语、简单短语和句柄
短语:设 \(G[Z]\) 是一文法,\(w = uxy\) 是一句型,如果有 \(Z \stackrel{*} \Rightarrow xUy\) 且 \(U \stackrel{+} \Rightarrow u\) ,称 \(u\) 是一个相对于非终结符号 \(U\) 的句型 \(w\) 的短语
- 若有 \(Z \stackrel{*} \Rightarrow xUy\) 且 \(U \Rightarrow u\) ,称 \(u\) 是一个相对于非终结符号 \(U\) 的句型 \(w\) 的简单短语
- $ U V_n, u V^+, x, y V$
- 短语是针对句型和非终结符号的
句柄:任一句型的最左简单短语称为句型的句柄
通过画出句型的语法树可以直观方便地找到句型的短语、简单短语和句柄
练习 2-3
2.4 语法树和二义性
2.4.1 推导与语法树
- 推导过程与语法树的生成
- 推导过程不同,但是最终生成的语法树是完全相同的
- 推导过程仅反应推导顺序
- 当然,并非所有文法都具有语法树唯一性
- 子树与短语
- 摆了
- 由树构造推导
- 摆了
2.4.2 文法与二义性
文法的二义性:
- 文法中的某个句子存在不同的语法树
- 有两个不同的最右(最左)推导
- 有两个不同的最左归约(规范归约),即归约中某些规范句型的句柄不唯一
消除二义性:
- 根据条件修改编译算法,如定义优先级
- 根据条件直接修改文法
练习 2-4
2.5 符号串的分析
前面讨论了用已知文法推导和产生句子,下面讨论确定给定符号串是否是文法的句子
即已知文法 \(G[Z], s \in V^*_t\) ,判定 \(s \in L(G[Z])\)?
主要分为自顶向下和自底向上分析
自顶向下分析 | 自底向上分析 | |
---|---|---|
2.6 有关文法的实用限制
不能有有害规则、多余规则
压缩文法:每一个终结符都满足条件一和条件二
2.7 扩充的 BNF 表示和语法图
- 扩充的 BNF 表示
优点:
- 表示方便
- 消除左递归,便于自顶向下分析
具体规则:
- 花括号
{}
:\(\{t\} ^m _n\),表示符号串 \(t\) 可以重复连接 \(n\) 到 $ m$ 次,若省略 \(n、m\) 表示可连接 \(0\) 次到任意多次 - 方括号
[]
:\(\[t\]\),表示符号串 \(t\) 可有可无,\([t] = \{t\} ^1 _0\) - 圆括号
()
:在规则中提取因子
- 语法图
2.8 文法和语言分类
乔姆斯基对文法的定义
文法和语言分类
- \(0\) 型文法(短语结构文法)
- \(1\) 型文法(上下文敏感文法、上下文有关文法)
- \(2\) 型文法(上下文无关文法)
- \(3\) 型文法(正则文法)
- 与能为有穷状态自动机接受等价
文法的限制性依次增强
第三章 词法分析程序的设计
词法分析程序又称词法分析器或扫描器
大多数程序语言的词法规则属于乔姆斯基 \(3\) 型文法
通过状态图方便地设计词法分析程序
3.1 词法分析器的功能及实现方案
- 识别输出单词,并检查错误
- 转换数字
- 删去空格、换行、制表、注释等空白符
将字符串表示的源程序转化为以单词表示的源程序
3.2 单词的种类及词法分析程序的输出形式
单词的种类:见前
词法分析程序的输出形式 一般采用二元式,如
单词类别 单词值 整型 58 保留字 for 按单词种类分类或一符一类
3.3 正则文法及其状态图
画状态图并以之构造词法分析器
3.3.1 状态图
一个有向图,其中
圆圈标识的结点表示状态,状态之间用有向弧连接,弧上的标记(字符)代表有向弧的射出结点状态下可能出现的输入字符。
包含有限个结点,包含初态和终态,终态用双圈表示
3.3.2 使用状态图分析字符串x
自底向上
3.4 词法分析程序的设计与实现
3.4.1 文法及其状态图
3.4.2 词法分析程序的构造
将语义动作添加到状态图中,并使每一个状态都对应一小段程序
3.4.3 词法分析程序的实现
先确定具体输出形式
输出形式
采用二元式
1 | int getsym() { // 返回类别码 |
第4章 语法分析
主要介绍自顶向下手工实现递归下降
4.1 自顶向下分析方法
一般方法:带回溯的自顶向下分析方法
本质上是一种试探方法,反复使用不同规则匹配输入串
存在的问题:
左递归问题——用扩充 BNF 文法改写
提因子规则:消除左递归,压缩文法长度
替换规则:\(U::=x|y|\dots|z|Uv \rightarrow U::=(x|y|\dots|z)\{v\}\)
改写为右递归:\(P::=Pa|b \rightarrow P::=bP ^{\prime}\) 和 \(P \prime ::=aP^{\prime} | \varepsilon\)
一般的改写算法:
事实上就是先递归代入,再消除左递归,最后化简
回溯问题
4.2 递归下降分析法
主要做法:对文法中每一个非终结符号 \(U\) 都编写一个子程序完成其所对应的语法成分的分析和识别任务。子程序用该非终结符号的规则的右部符号串去匹配输入串
4.3 基于递归下降分析法的语法分析程序构造
nextsym
:扫描下一个符号并将类别码储存在
sym
中
error
:调用出错处理程序打印错误信息,跳过分析过程中有错误的一段源程序
第五章 符号表管理技术
分析阶段:维护符号表
综合阶段:利用符号表生成目标代码
5.1 概述
符号表:记录源程序中各种标识符的特性信息的表格
与遍数的关系:
- 多遍扫描中在词法分析阶段填写,其他属性在语义分析和代码生成阶段填入
- 一遍:语义分析和代码生成
标识符在源程序中的每一次出现都要与符号表打一次交道
若允许隐式声明,必须按首次引用处理
5.2 符号表的组织和内容
5.2.1 符号表的结构和内容
标识符的种类:如简单变量、函数、过程、数组、标号、参数等。
类型:如整型、实型、字符型、指针等(一般用编码表示)
性质:变量形参、值形参等
值:标识符所代表的数值。
地址:标识符所分配单元的首地址或地址位移。
大小:所占的字节数。
作用域的嵌套层次:分程序结构语言中,标识符声明所在的分程序的层次。
标识符声明的源程序行号
标识符引用的源程序行号
以字母顺序排列的链域
链域指向按字母排序的下一个标识符在符号表中的位置,便于产生按字母顺序排列的标识符交叉引用表
故如果不产生交叉引用表可以删去
对于一些特殊的名字,还要考虑较多的信息,如:
- 对于数组,要考虑维数、上下界值、计算下标变量地址所用的信息以及数组元素类型等
- 对于记录(结构、联合),要考虑域的个数,每个域名、地址位移类型等
- 对于过程或函数,要考虑形参个数、所在层次、函数返回值类型、局部变量所占空间大小等
- 对于指针,要考虑所指对象类型等
5.2.2 符号表的组织方式
- 统一符号表:按各种信息的并集设计表项
- 方便插入、查找
- 浪费空间
- 按标识符建立符号表:分别设计表项
- 节约空间
- 插入、查找开销大
- 折中:存储大部分共同信息,特殊信息另附表,二者用指针连接
经典的时间和空间的 tradeoff
符号名的存储方法:
- 预设长度
- 速度快,空间利用率低
- 放置描述符
- 速度慢,节约空间
就是数组实现方式和链表实现方式的区别
5.3 非分程序结构语言的符号表组织
主要介绍了散列技术,摆了
5.4 分程序结构语言的符号表组织
5.4.1 标识符的作用域及基本处理方法
作用域局限在所定义的最小模块中
建表和查表的基本处理方法:
- 在声明部分读到标识符时
- 查询所在程序单元符号表
- 已存在同名标识符——报错
- 不存在同名标识符——插入
- 查询所在程序单元符号表
- 在可执行部分读到标识符时
- 查询所在程序单元符号表
- 已存在同名标识符——已声明
- 不存在同名标识符
- 逐层查找外层符号表
- 已存在同名标识符——已声明,读取信息供使用
- 未存在同名标识符——使用了未声明的标识符
- 逐层查找外层符号表
- 查询所在程序单元符号表
- 语言预定义的标识符
- 放在最外层符号表
- 分程序开始
- 定位
- 分程序结束
- 重定位
5.4.2 定位和重定位
其嵌套方式显然对应着栈结构
5.4.3 符号表的组织方式
- 栈式符号表
- 遇到标识符声明——压栈
- 分程序结尾——仅弹栈而不彻底删除
- 性能较差,仅用于符号表较小时
- 散列符号表的栈式实现
第六章 运行时的存储组织及管理
6.1 静态存储分配
对于编译时可以确定其存储空间大小的数据,可以在编译时就给它们分配固定的存储空间
其余数据则将分配存储过程留待目标程序运行时动态地进行
6.2 动态内存分配
6.2.1 活动记录
运行栈上的动作和普通栈上的动作相同。当进入一个模块时,就在运行栈的栈顶创建一个专用数据区,通常称为活动记录(Activation Record)。活动记录的开始位置称为基
注意栈的增长方向
6.2.2 参数区
6.2.3 display 区
6.2.4 运行时的地址计算
6.2.5 递归过程的处理
6.3 内存垃圾回收器
需要从编译器、操作系统甚至处理器芯片中获得信息
分析当前程序运行时产生的变量根集(root set)进行可达性分析
变量根集指在程序运行的某一时刻运行栈上所有变量或对象的集合,再加上当前程序的全局变量、静态变量等变量或对象
6.3.1 引用计数
reference counting
指向该对象的引用被创建——count+1
已经存在的指向某对象的引用被删除或重写——count-1
e.g. \(P = Q\) ,Q 指向对象的计数值-1,P 指向对象的计数值+1
回收一个对象可能会传递性地对其他对象的计数器进行-1操作,最后回收那些计数器变成0的对象
对于大部分高性能系统,引用计数已经被跟踪型垃圾回收器替代
6.3.2 标记和清除垃圾回收器
两阶段回收垃圾:
- 从对象根集开始遍历整个指针关系图,通过标识位区分死对象和活对象
- 垃圾回收,不移动内存堆中的对象。故主要弊端是内存碎片
6.3.3 标记紧缩算法
在标记清除回收器的基础上引入紧缩技术
6.3.4 拷贝回收算法
- 空间利用率低
6.3.5 分代垃圾回收器
第七章 源程序的中间形式
也即中间代码
7.1 波兰表示
中缀转后缀算法:
- 扫描到操作数,直接输出
- 遇到操作符,与栈顶元素比较优先级
- 栈顶 > 栈外,输出栈顶
- 反之,操作符入栈
波兰表示不便优化
7.2 N-元表示
三元式:<操作符1>,<操作数1>,<操作数2>
一组三元式,前面三元式的计算结果可用该三元式的编号表示
四元式:<操作符1>,<操作数1>,<操作数2>,<结果>
7.3 抽象语法树
非叶结点:操作符
叶结点:操作数
与前两种表示的联系:后续遍历可以得到后缀表达式,
7.4 抽象机代码
摆了
第八章 错误处理
8.1 概述
编译程序的错误处理能力包括:
- 检查出各类错误的能力
- 准确地指出出错位置及错误性质的能力
- 通过一次编译能将源程序中的错误尽可能都找出来的能力
- 具有一定的错误改正的能力
- 遏止重复错误信息的能力
8.2 错误的分类
- 语法错误:不符合语法(词法)规则
- 如漏掉一半括号等
- 语义错误:不符合语义或超越具体计算机的限制
- 如标识符先声明才能引用,形参实参个数匹配
- 溢出错误,如计算结果太大;符号表、数据存储区等溢出
8.3 错误的检查与报告
一些语义错误要由目标程序检查出来
- 如下标越界
- 处理方法一般是编译时生成有关检验指令
报告错误的两种方式
- 分析完再打印,需要单独开辟空间
- 边分析边打印,有可能检测不出来
8.4 错误处理技术
两种错误处理办法
- 错误改正
- 太难了没有编译器做到
- 错误局部化处理
- 暂停并跳过对出错部分的分析
- 实现:在编译程序中设置专用变量存放指定的右界符
遏制重复的错误信息
建立出错名字表,类似如下代码段
1
2
3
4
5
6
7// demo.java
if (errorMap.containsKey(unDefinedModifier)) {
// do something
} else {
put(unDefinedModifier, errorMessage);
// print something
}设置标志位,类似
isVisited
第九章 语法制导翻译技术
翻译文法、属性文法、自顶向下语法制导翻译技术
9.1 翻译文法
翻译文法:终结符号集由输入符号和动作符号组成的上下文无关文法。
活动序列:翻译文法确定的语言中的符号串
9.2 语法制导翻译
形式上,将翻译看成偶对的集合,第一个元素是输入语言中的符号串,第二个元素是翻译该符号串时的动作序列或执行动作序列所规定的操作后生成的新符号串
即 符号串 \(\rightarrow\) 动作序列
或 符号串 \(\rightarrow\) 动作序列生成的新符号串
在翻译文法是符号串翻译文法的时候可以认为二者同义
语法制导翻译:给定一输入符号串,根据输入文法获得翻译该符号串的动作序列,并执行该动作序列所规定的动作的过程
翻译文法定义的翻译
输入序列:表示从活动序列中删掉所有动作符号而得到的符号序列 动作序列:表示从活动序列中删掉所有输入符号而得到的符号序列
你俩在这玩相爱相杀是吧
9.3 属性翻译文法
9.3.1 综合属性
将翻译文法中无值的符号扩充为有值的,值部分称为符号的属性
产生式中左部符号的属性值是通过计算右部符号的属性值得来的
9.3.2 继承属性
9.4 自顶向下语法制导翻译
// to be continued
这章好抽象啊,不知道对于实验需要的内容有多大帮助,反正连滚带爬读完了,理论内容先放一放
第十章 语义分析和代码生成
10.1 语义分析的概念
处理上下文有关文法的方式:用专门的语义动作来补充上下文无关分析器的动作,即借助语义分析
语义分析要处理的问题
- 我没读出来作者的意思,所以这条鸽了
- 表达式和赋值语句操作数的类型一致性检查
- 分析语义并做相应的语义处理
10.2 栈式抽象机及其汇编指令
table
10.3 声明语句的处理
编译器分离出每个实体,并填符号表
声明后,可以使用符号表中信息
- 检查对所声明的实体引用是否正确
- 利用已声明实体的特性信息,为给定源程序生成特定的目标代码
10.3.1 常量类型
10.3.2 简单变量
1 | void staticvardef(int t, int i, char *n) |
10.3.3 数组变量
编译程序在处理数组声明时将建立一个模板,以便在执行期间能够间接引用该数组的元素。
- 静态数组——在编译期间建立
- 动态数组——在编译时仅为模板的建立分配一个空间,而模板本身将在运行时建立
数组绝对地址的计算
10.3.4 记录变量
用来引用几个不同名字所组成的实体
10.3.5 过程声明
10.4 表达式语句
将表达式的操作数装载到操作数栈栈顶或寄存器,执行操作后将结果保留在操作数栈栈顶或寄存器
10.5 赋值语句
如果支持对数组的赋值,需要先找到数组的地址,继而使用内部计数器增值的方法产生对所有数组元素的引用
10.6 控制语句
10.6.1 if
语句
10.6.2 case
语句
10.6.3 while
语句
10.6.4 for
语句
10.7 过程调用和返回语句
// to be continued
10.8 输入/输出语句
// to be continued