编译原理
第一章 引论
1.1 编译器简介
1、什么是编译器?
编译器(编译程序)是一个程序,它读入源语言编写的程序并将其翻译成一个与之等价的以目标语言编写的程序。
编译器能够向用户报告源程序中出现的错误。
<!--more-->
主要功能:
1、翻译
2、报错
等价:在功能上等价
即源程序后翻译后的目标语言程序功能要一致。
源语言:通常指高级程序设计语言。例如C++、Java等
目标语言:指汇编语言或机器语言。
2、为什么要用编译器?
简单来说就是为了将形形色色的语言翻译成可以在计算机上运行的0、1串。
1、首先来看看计算机语言的发展:
计算机语言的发展是一个从难到易,从复杂到简单的过程。
机器语言:效率高,编写困难
-》
汇编语言:可读性差,机器相关
-》
高级语言:机器无关,易读易编
举例:
C70600000002-》MOVX,2-》x=2
3、计算机语言的翻译方式
1、解释程序(Interpreter)——口译 (运行时逐步编译,逐步执行)
接受某高级语言的一个语句输入,进行解释并控制计算机执行,马上得到执行结果,然后再执行下一语句。
运行时由解释器逐语句执行。
程序执行效率不高
代表语言:html,javascript。
2、编译程序(Compiler)——笔译 (先全部编译,再全部执行)
将高级语言程序转换成为低级语言程序,再执行。
使用编译器编译后生成计算机硬件可直接执行的指令。
编译程序是现今任何计算机系统的最重要的系统程序之一。
人们普遍用各种高级程序设计语言写程序
代表语言:C&C++,C#,Java
4、编译器分类:
不同的源语言、目标语言划分
构造方法或要实现的功能划分
基本任务是相同的
基本方法、技术是相同的
5、编译示意图:
汇编语言-机器指令:汇编(或交叉汇编)
程序设计语言-汇编语言或机器指令:编译(或解释)
高级语言之间:转换(或预编译)
逆向:反汇编、反编译
1.2 编译器组成
1、从功能上划分(6+2):
2、编译器的工作过程(类比实际生活中的举例):
英文到中文的翻译过程:
1识别出句子中一个个的单词 ——词法分析器
2分析句子的语法结构 ——语法分析器
3根据单词和句子的含义进行初步翻译 ——语义分析器+中间代码生成器
4对译文进行修饰 ——代码优化器
5写出最后的译文 ——代码生成器
3、词法分析
1、主要任务:
将源程序视为字符流,从左到右扫描源程序,识别记号及其有关属性,并保存。
记号(token),又称为单词,是高级语言中有实际意义的最小语法单位,它由字符构成。
2、其他任务:
删除无用的空白字符及其它与输入介质相关的非实质性字符(空格、回车等)
删除注释
进行词法检查,报告所发现的错误
4、语法分析
1、主要任务:
以词法分析程序输出的记号流为输入, 根据语言的语法规则,将记号组成各类语法单位,如短语、子句、语句、过程、程序。
2、其他任务:
指出语法错误,指导翻译。
3、语法规则(文法)
语言的规则,又称文法,规定如何组词造句。
4、方法:试图为源程序构造一个语法树
所谓语法树只是逻辑概念上的,并不是在机器内真要存储一个树形结构。
5、语义分析:
程序的语义就是它的“意思”。
一种程序设计语言具有语法和语义两个特征
语法特征描述各成份的形式或结构
语义特征描述各语法成份的含义与功能,即规定它们的属性或在执行时应进行的运算或操作 。
只有进行了语义分析,才能让计算机知道,应进行何操作或运算 。
语义处理尚无公认的方法来系统地描述。(语法制导翻译)
语义分析:声明和类型检查。(a=b+c;)
举例:
6、代码优化:
1、对代码进行等价变换以求提高执行效率
提高运行速度
节省存储空间
2、优化方法分类:
与机器无关的优化:常量合并、强度削减、代码外提
与机器有关的优化:寄存器分配、存储策略、任务划分
7、代码生成:
1、主要任务:将中间代码转换成目标机上的机器指令代码或汇编代码
首先确定源语言各种语法成份的目标代码结构
再根据需要制定中间代码到目标代码的翻译策略
这部分程序对具体机器的依赖性很强,需具体情况具体分析
2、目标代码的形式
具有绝对地址的机器指令
汇编语言形式的目标程序
模块结构的机器指令(需要链接程序)
8、符号表管理(symbol table management)
编译过程中,需经常收集、记录或查询程序中所出现的各种量的有关属性(信息)。
为此,编译程序需要建立一批不同用途的表格(常数表、变量表、关键字表等)
除此之外,根据不同的分析方法,编译程序还保持一些专用的表格(LL分析表、LR分析表、状态矩阵等)
合理地组织各种表格,恰当地选用相应的造表和查表算法是提高编译程序工作效率的有效途径(哈希表、链表)
9、错误处理(error handling)
程序中出现错误是难免的
词法:拼写……
语法:语句结构、表达式结构……
语义:类型不匹配……
完善的编译程序应具有很强的查错能力,并能准确地报告源程序中错误的种类及位置。
除报错外,编译程序还应生成一些附加的注释信息,它有助于程序设计人员调试程序。
10、综上,编译器工作的图解与举例:
图解:
举例:
1.3 编译器相关程序
1、一个完整的语言开发环境除了编译器之外,往往还有很多其它程序辅助开发者工作
编辑器(editor)
预处理器(preprocessor)
汇编器(assembler)
连接器(linker)
装入器(loader)
调试程序(debugger)
描述器(profiler)
项目管理程序(projectmanager)
1.4 编译器的其他问题
1、编译器的“分析-综合”模型
1、编译过程可分为分析、综合两个部分
将分析源程序以计算其特性的编译器操作归为编译器的分析部分
将生成翻译代码时所涉及到的操作称作编译器的综合部分
词法分析、语法分析和语义分析均属于分析部分
代码生成是综合部分
在优化中,分析和综合都有
2、编译器的“前端-后端”划分
与将之分成分析和综合两部分类似。
1、前端(front end):只依赖于源语言,与目标机器独立。
词法分析、语法分析、语义分析、中间代码生成、机器无关的代码优化、符号表建立、相关错误处理
2、后端(back end) :只依赖于目标机器机器
有关的代码优化、代码生成、符号表操作、相关错误处理
3、前端-后端划分的作用
1、便于编译器的移植和改写
这一思想正处于研究中。
3、编译器的“遍(pass)”
1、定义
编译器将其若干阶段组合起来,对源程序进行一次处理的过程。
2、单遍编译:适用于小程序
编译速度快、效率高
编译程序复杂、占用存贮空间大
目标程序不优化
3、多遍编译:适用于大程序
编译程序较容易、存贮空间节省
目标程序优化
有重复工作,效率低,耗时长
1.5 自举和移植
1、第一个编译器是如何得到的?——自举(bootstrapping)
编译器是一个程序(软件),它本身也是采用某种语言编写,也需要编译。
那它作为一个程序,肯定也是人们无中生有,需要人来创造编写的,因此思考以下问题:
第一个编译器如何编译得到的?
先有鸡还是先有蛋?
第一台机床如何得到的?
回溯历史,寻找答案:
第一个Fortran编译器花了18年。
用汇编或机器语言手工编写一个不完善的S语言的编译器。
用这个编译器来编译用S语言编写的S语言的编译器(自举, bootstrapping),多次后得到一个完善的编译器。
发展历程图解:
图解说明:
起初使用S语言编写了从S语言-》H语言的编译器。
然后使用H语言手工编写了从S语言-》H语言的编译器。
最后不断完善,得到了第一个Fortan编译器。
* 不得不说第一台编译器的手工编写是真牛啊!
2、编译器的T型图表示:
较为原始的T型图:
S:编译器能编译的源语言
I:编译器自身的实现语言
T:编译器最终得到的目标语言。在通常的编译器中,I和T相同
现在常用的编译器T型图:
这个图的意思是:
在以H语言编写的编译器上,可以将S语言等价编译为H语言。
有了编译器,可以用S语言编写任何想要的程序,这些程序编译后即可执行。
H:目标语言H(机器语言)
2、移植
有了编译器之后,为了编译器的广泛使用,思考以下问题:
有了A语言在H机器上的编译器,如何得到B语言在H机器上的编译器?
有了A语言在H机器上的编译器,如何得到A语言在M机器上的编译器?
有了A语言在H机器上的编译器,如何得到B语言在M机器上的编译器?
以上问题都体现了移植的思想。移植也是编译器中一个比较重要的思想。一起来看一下何为移植吧!
1、有了A语言在H机器上的编译器,如何得到B语言在H机器上的编译器?
问题分析:
已知条件1:
首先有了A语言在H机器上的编译器是指:
在H语言编写的编译器上,可以将A语言编译成H语言,如下图:
已知条件2:
算是一个简单的推论结果吧。
在已知条件1的基础上,我们现在可以将A语言编译成H语言。
也就是说:
我们可以将任何一门语言在使用A语言编写的编译器上将该语言编译为H语言。
这应该不难理解,因此以此处的B语言为例,就能得到下图:
想要结果:
其次B语言在H机器上的编译器是指:
想要得到使用H语言编写,可以将B语言编译成H语言的编译器,如下图:
分析了已知条件和想要的结果,我们就能进行问题回答了。
问题解决:
图片解答:
图解说明:
符号表示:源语言->编译器的底层语言->目标语言
B->A->H // 已知条件1:A可以作为编译器将B编译为H
A->H->H // 已知条件2:H可以作为编译器将A编译为H
B->A->H->H // 可推出:B可以先被A编译为H,然后被H编译为H
综上简化结果为:B->H->H。
2、有了A语言在H机器上的编译器,如何得到A语言在M机器上的编译器?
道理与上一题类似,只不过稍微复杂一点,就不详细分析了。简单分析一下吧那就~
已知条件:
A->H->H
A->A->M // 这是根据题目条件默认已知的,这一点的条件提取比较重要,要 转换思路!
想要结果:
A->M->M
问题解决:
图解说明:
A->A->M // 条件1
A->H->H // 条件2
A->H->M // 推论1(将条件1中间的A换为H)
A->M->M // 结论:(将推论1与条件2结合)
综上可得。
3、有了A语言在H机器上的编译器,如何得到B语言在M机器上的编译器?
再需要分析吗,算了一次分析完吧,最后一道例题喽~
已知条件:
直接看出来的条件:
A->H->H
间接隐含条件:
B->A->H
B->A->M
B->B->M
//其实间接隐含条件的提取遵循一个原则:
// 箭头中部(编译器的底层语言)不含我们要求的XX机器编译器。
想要结果:
B->M->M
问题解决:
B->A->M
A->H->H
B->H->M
B->B->M
B->M->M
就不写注释喽,你试着自己写一下吧~
3、编译器的构造工具
第三章—词法分析
编译的第一阶段
从源程序中产生记号序列
x := y + z * 60.0 ;
id1:= id2 + id3 * 60.0 ;
标示符 赋值符 标示符 运算符 标示符 运算符 常量
一套描述记号结构的格式说明
以格式说明为依据的识别方法
本质:模式识别/匹配
3.1 词法分析器的功能
词法分析器的主要任务:
输入源程序,输出记号(token)。
把构成源程序的字符串转换成记号的序列
记号是编译器的以后部分(通常是分析程序)处理的逻辑单元
辅助任务:
过滤源程序中的无效字符(注释、空格、制表符、换行符)
检测、处理词法错误
出错位置记录
3.1.1 使用方式
1、方式:
1、单独一遍扫描
2、作为语法分析器的子程序
3、并行方式
2、词法分析和语法分析联系很紧密。
3、划分词法分析和语法分析的意义:
简化编译器的设计
提高编译器的效率
增强编译器的可移植性
便于使用工具来自动构造
3.1.2 记号(单词、token、词法单元)
多个教材有多种不同的叫法。
1、定义:
编译器的以后部分(通常是分析程序)处理的逻辑单元。
也被称为单词、语法单元、token等。
2、划分依据:
高级语言中有实际意义的最小语法单位。
再细分就没有语法意义了,没有必要。
举个例子:const pi = 3.1416
注意:记号不仅仅是单词!!!它还包括模式、词素。
3、模式和词素
模式:某类记号的构成规则
词素:记号的一个实例,为字符序列
常用记号类型:(对应不同模式)
关键字(保留字、基本字):if、while
运算符: +、-、*
标识符:pi、anything
分界符:{,},(,),;
常数:5,a,3.1416