1 引论
海尔米写
发布日期:2021-06-29 18:38:13
浏览次数:2
分类:技术文章
本文共 6923 字,大约阅读时间需要 23 分钟。
文章目录
1.2缩译过程和编译程序的结构
1.2.1编译过程概述
- 一个编译程序的整个工作过程是划分成阶段进行
- 每个阶段将
- 源程序的一种表示形式
- 转换成另一种表示形式
- 图1.3是典型的划分方法
- 划分成
- 词法分析、语法分析、语义分析、
- 中间代码生成、代码优化和目标代码生成
1.词法分析
- 任务是
- 从左到右一个字符一个字符地读源程序,
- 对构成源程序的字符流
- 扫描和分解,
- 识别出一个个单词(也称单词符号、符号)。
- 单词指逻辑上紧密相连的一组字符,
- 标识符是由字母字符开头,后跟字母、数字字符的字符序列组成的一种单词。
- 保留字是一种单词,还有算符、界符
- 5字符b、e、g、i和n构成称保留字的单词begin
- :和=构成表示赋值运算的符号:=
- 空格在词法分析阶段被滤掉
- idl、id2和id3表sum、 first和 count这3个标识符的内部形式,
- 经词法分析后上述赋值语句
- sum:= first+ count*10表示为idl:=id2+id3*10。
2 语法分析
- 任务是在词法分析的基础上
- 将单词序列分解成各类
- 语法短语,如“程序”、“语句”、“表达式”
- 也称语法单位
- 可表示成语法树,如上述程序段中的单词序列
- idl :=id2 +id3 * 10
- 语法分析
- 知其是Pascal的赋值语句,
- 表示成图1.4的语法树
- 图1.5的简捷形式的语法树
- 语法分析依据的是
- 语言的语法规则,即描述程序结构的规则
- 通过语法分析确定整一个输入串是否构成
- 一个语法上正确的程序。
- 程序的结构通常由递归规则表示的,
- 可用下面的规则来定义表达式:
- 任何标识符是表达式。
- 任何常数(整常数、实常数)是表达式。
- 若表达式1和表达式2都是表达式,那么
- 表达式1+表达式2
- 表达式1兴表达式2
- (表达式1)
- 都是表达式
- 语句也可递归定义
- 标识符:=表达式是语句
- while(表达式)do语句 和
- If(表达式)then语句else语句
- 都是语句。
- id1:=id2+id3*10能表示图1.4所示语法树
- 依据的是赋值语句和表达式的定义规则。
- 词法分析仅对
- 源程序进行线性扫描即可完成,
- 如识别标识符,因为标识符的结构是字母打头的字母和数字串,
- 只要顺序扫描输入流,遇到既不是字母又不是数字的字符时,
- 将前面所发现的所有字母和数字组合在一起
- 构成标识符单词即可。
- 线性扫描不能用于识别递归定义的语法成分
- 不能用此办法去匹配表达式中的括号
3.语义分析
- 审查源程序有无语义错误,
- 为代码生成阶段收集类型信息。
- 类型审查,审査每个算符是否具有语言规范允许的运算对象,当不符合语言规范时,编译程序应报错。
- 有的编译程序对实数用作数组下标报错
- 某些语言规定运算对象可被强制转换数据类型,
- 当二目运算施于整型和实型时,编译程序应将整型转成实型
- 不认为源程序错
- sum:= first+count*10中, count是实,10是整,
- 语义分析时类型审査后
- 语法分析所得到的分析树上
- 加一个语义处理结点inttoreal,
- 整型10变成实型10.0的一目算符
4.中间代码生成
- 语法分析和语义分析阶段后,
- 源程序变成一种内部表示形式,
- 这种内部表示形式叫中间代码
- “中间代码”是种结构简单、含义明确的记号系统,
- 此记号系统多种多样的形式
- 原则为两点:
- 易生成;
- 易将它翻译成目标代码。
- 很多编译程序
- 近似“三地址指令”的“四元式”中间代码 (运算符,运算对象1,运算对象2,结果)
- sum:= first+ count * 10
- 生成图1.7的四元式序列,
- t i ( i = 1 , 2 , 3 ) t_i(i=1,2,3) ti(i=1,2,3)是编译程序生成的临时名字,
- 存放运算中间结果
5.代码优化
- 对前一阶段产生的中间代码变换或改造
- 使生成的目标代码省时和省空
- 图1.7可换为图1.8,仅剩两个四元式而执行同样计算
- 此阶段将10转换成实型数的代码化简了
- t 3 t_3 t3仅用来将其值传递给idl,也可被化简,
- 这是优化工作的两个方面
- 公共子表达式的删除、强度削弱、循环优化等优化在10章详细
6.目标代码生成
- 把中间代码变换成特定机器上的绝对指令代码
- 或可重定位的指令代码
- 或汇编指令代码。
- 是编译的最后阶段,与硬件系统结构和指令含义有关,
- 工作很复杂,涉及硬件系统功能部件的运用、
- 机器指令的选择、
- 各种数据类型变量的存储空间分配
- 及寄存器和后缓寄存器的调度等。
- 用(R1和R2),
- 将图1.8生成如图1.9的某种汇编代码。
- 将id3的内容送至寄存器R2,
- 其与实常数10.0相乘,#表明10.0处理为常数,
- id2移至寄存器R1,
- R1和R2中的值相加,
- 将寄存器R1的值移到idl的地址。
- 上述编译过程的阶段划分是一个典型处理模式,
- 并非所有的编译程序都分成这样几阶段,
- 有些编译程序不生成中间代码,
- 有些不优化
- 有些最简单的编译程序在语法分析的同时
- 产生目标指令代码,
- 如1.4节的PL/0语言编译程序。
- 多数实用的编译程序都含上述几阶段
1.2.2 编译程序的结构
- 6阶段由6模块完成,
- 词法分析程序、语法分析程序、等!
- 一个完整的编译程序还
- 表格管理
- 出错处理
- 图1.10:典型的编译程序结构框图
- 表格管理和出错处理
- 与6阶段都联系。
- 编译过程中
- 源程序的各种信息
- 被保留在种种不同的表格里,
- 编译各阶段的工作都
- 涉及构造、査找或更新有关的表格,
- so要有表格管理的工作。
- 若发现源程序有错
- 应报告错误的性质和错误发生的地点,
- 且将错误所造成的影响限制在尽可能小范围内,
- 使得源程序的其余部分能继续编译
- 有些编译程序还能自动校错,
- 这些由出错处理程序完成
1.2.3编译阶段的组合
- 1.2.1的编译阶段的划分是编译程序的逻辑组织。
- 编译过程分为前端和后端,
- 前端的工作依赖于源语言而与目标机无关。
- 前端含词法、语法、语义和中间代码生成,
- 某些优化工作也可在前端做,
- 还包括与前端每个阶段相关的出错处理和符号表管理
- 后端:依赖于目标机而不依赖于源语言,
- 只与中间代码有关的那些阶段的工作,
- 即目标代码生成以及相关出错处理和符号表操作。
- 按这种组合方式实现编译
- 某一编译程序的前端加上
- 相应的后端
- 则可为不同的机器构成同一个源语言的编译程序。
- 不同语言编译的前端生成同一种中间语言,
- 再用一个共同后端,
- 则可为同一机器生成几个语言的编译程序
- 一个编译过程可由一遍或多遍完成。
- “遍”是对源程序或其等价的中间语言程序
- 从头到尾扫描并完成规定任务的过程。
- 每一遍可完成上述一个或多个阶段
- 一遍可只完成词法,一遍完成词法和语法,甚至一遍完成整编译。
- 对多遍的编译程序,第一遍的输入是源程序,最后一遍的输出是目标程序,
- 其余是上一遍的输出为下一遍的输入。
- 实际的编译系统的设计中,
- 编译的几个阶段的工作究竟咋组合,
- 即编译程序究竟分几遍,
- 参考因素主要是源语言和机器(目标机)的特征。
- 源语言的结构直接影响编译的遍的划分
- PL/1或 ALGOL68允许名字的说明
- 出现在名字的使用后,
- 看到名字之前不便为包含该名字的表达式生成代码
- 这种至少两遍才生成代码。
- PL/1或 ALGOL68允许名字的说明
- 机器的情况,即编译程序工作的环境也影响编译程序的遍数的划分。
- 多遍比一遍的少占内存,
- 遍数多一点,
- 整个编译程序的逻辑结构更清晰,
- 但也意味增加读写中间文件的次数,
- 消耗多时,比一遍慢
1.3解释程序和一些软件工具
1.3.1解释程序
- 编译程序
- 把高级语言翻译成某个机器的汇编语言或二进制代码
- 此二进制代码在机器上运行生结果
- 因此编译程序使得程序员可先准备好一个在该机器上运行的程序,
- 然后此程序便会以机器的速度运行。
- 但不把整个程序全部翻译完成后,
- 此程序不能运行
- 编译和运行是独立分开的
- 但在交互环境中,
- 并不需将这两个阶段分开
- 介绍另种语言处理程序,解释程序,
- 它不需在运行前先把源程序翻译成目标代码,
- 也可实现在某台机器上运行程序并生结果。
- 解释程序接受某个语言的程序并立即运行这个源程序。
- 一个个的获取、分析并执行源程序语句,
- 一旦第一个语句分析结東,源程序便开始运行且生结果,
- 适合程序员以交互方式
- 希望在获取下一个语句前了解每个语句的执行结果,
- 允许执行时改程序
- 著名的解释程序
- BASIC语言解释程序、
- LISP语言、
- UNIX命令语言(shell)解释程序、
- SQL解释程序
- Java语言环境中(图1.15)的BYTECODE解释程序。
- 解释程序的输入:源程序和源程序的初始数据(输入数据),
- 不生成目标代码,直接出结果。
- 编译程序和解释程序的存储组织大不同。
- 编译程序处理时,在源程序被编译的阶段,
- 存储区中要为源程序(中间形式)和目标代码开辟空间,
- 存放编译用的各种表格,如符号表
- 目标代码运行时,
- 存储区中主要是目标代码和数据,
- 编译所用的信息都不再需
- 解释程序把源程序一个语句一个语句地语法分析,
- 转换为一种内部表示形式,放在源程序区。
- BASIC解释程序将
- LET和GOTO关键字表示为一个字节的操作码,
- 标识符用其在符号表的入口位置表示。
- 解释程序允许在执行用户程序时修改用户程序,
- 在解释程序工作的整个过程中,
- 源程序、符号表等内容始终存放在存储区中,
- 且存放格式要设计得易使用和改。
- 源程序的解释比运行等价的机器代码程序100。
- 程序运行速度重要时,不用解释
- 解释程序的空间开销也大
- 编译和解释是两类重要的高级语言处理程序。
- BASIC、LISP和Pascal,既有编译,也有解释。
- Java语言的处理环境既有编译程序,也有解释程序。
- 图1.15:Java语言处理环境
1.3.2处理源程序的软件工具
- 为提软件开发效率和质量
- 需一套软件开发过程所遵循的规范或标准,
- 应使用先进的软件开发方法,并有相应的软件工具的支持。
- 而这些软件工具的开发,其中很多要用到编译的原理和技术。
- 编译程序本身也是一种软件开发工具
- 有了它们才能使用编程效率高的高级语言来写程序。
- 为进一步提高编程效率,缩短调试时间,软件工作人员研制了许多针对源程序处理的软件工具,这些工具首先要像编译程序那样对源程序进行分析。
- 下面是工具的例子
1.语言的结构化编辑器
- 用户可用这种编辑器在语言的语法制导下编制出所需的源程序。
- 结构化编辑器不仅具有通常的正文编辑器的正文编辑和修改功能,且还能像编译程序那样对源程序正文分析。
- 结构化编辑器能够执行一些对正确编制程序有帮助的附加的任务。
- 检査用户的输入是否正确,能自动提供关键字,当用户输入if后,编辑器立即显示then并将这两个关键字之间必须出现的条件留给用户输入,
- 能检査begin或左括号与end或右括号是否相匹配。
- 结构化编辑器具有上述功能,既可保证编写出的源程序无语法错误,并有统一的、可读性好的程序格式,这无疑将会提高程序的开发效率和质量。
- 如Turbo-Edit、 Editplus和 Ultraedit
- 很多集成开发环境中也含
- 如JSBuilder中就有Java程序的结构化编辑器。
2.语言程序的调试工具
- 结构化编辑器只能解决语法错误
- 对一个已通过编译的程序来说,需进一步了解的是程序执行的结果与编程人员的意图是否一致,
- 程序的执行是否实现预计的算法和功能。
- 这种算法的错误或者程序没能反映算法的功能等问题
- 就要用调试器来协助解决。
- 有一种调试器允许用户使用源程序正文和它的符号来调试,即一行一行地跟踪程序,査看变量和数据结构的变化以进行调试工作。
- 当然,这些符号的信息必须由编译程序提供。
- 调试器的实现可以有很多途径。
- 其中一种是写一个解释器,以交互的方式翻译和执行每一行,它必须维护其所有的运行时的资源以保证在程序执行期间可以很容易地查询不同变量的当前值。
- 如果不通过解释手段调试,而是在编译之后的代码上进行调试,编译程序必须在目标代码(汇编)生成时同时生成特定的调试信息,例如,关联标识符和它表示的地址的信息,用于无歧义地引用一个声明了多次的标识符的信息等。
- 调试功能越强,实现越复杂,它涉及源程序的语法分析和语义处理技术。
3 程序格式化工具
- 程序格式化工具
- 分析源程序
- 并以使程序结构变得清晰可读的形式打印出来。
- 注释以一种专门的字形出现,
- 且语句的嵌套层次结构可以用缩排方式(齿形结构)表示
4.语言程序测试工具
- 两种:静态分析器和动态测试器。
- 静态分析器是
- 在不运行程序的情况下
- 对源程序进行静态分析,
- 以发现程序中潜在的错误或异常。
- 它对源程序进行语法分析并制定相应表格,检査变量定值与引用的关系,
- 如某变量未被赋值就被引用,
- 定值后未被引用,
- 或多余的源代码
- 等一些编译程序的语法分析发现不了的错。
- 动态测试工具也是先对源程序分析,
- 在分析基础上
- 将用于记录和显示程序执行轨迹的语句或函数
- 插入到源程序的适当位置,
- 并用测试用例记录和显示程序运行时的实际路径,
- 将运行结果与期望的结果比较,
- 帮助查问题。
5.程序理解工具
- 对程序进行分析,确定模块间的调用关系,记录程序数据的静态属性和结构属性,并画出控制流程图,帮助用户理解程序。
6.高级语言间的转换工具
- 硬件的不断更新
- 更好的语言推出为提高计算机的使用效率
- 已有的成熟软件如何在新机器新语言情况下使用?
- 为减少重新编制程序,
- 就要解决如何把一种高级语言转换成另一种高级语言,
- 乃至汇编语言转换成高级语言
- 这种转换工作要对被转换的语言进行词法和语法分析,
- 只不过生成的目标语言是另一种高级语言。
- 这与实现一个完整的编译程序相比工作量要少
1.4 PL/0语言编译系统
-
N.Wirth编写
- 编译程序和解释程序组成
-
源语言为PL/0
- 目标语言是个类P-code
-
本书介绍编译技术和方法时
- 会以PL/0编译程序的相关内容为例
- 来说明这些方法如何在实践中应用
- N.Wirth用的Pascal(附录A.1),
- 本书PL/0编译系统的C(附录A.2)
- C版与Pascal版结构一致,函数(过程)、变量等用同样名字
- 附录A中代码电子版可从清华出版社网站中的本书相关电子资源中下载
- 本书电子资源中还包括Java版,也是Pascal的翻版
- 后续涉及PL/0编译程序的例子中均指C
- PL/0语言编译系统的构成
- PL/O编译程序的源语言和目标语言
- 最后PL/O编译程序的基本组成
- PL/0编译程序的实现细节在后续章节中讨论
1.4.1 PL/0语言编译系统构成
- PL/O编译程序和类P-code解释程序
- PL/0语言程序被PL/O编译程序转为等价的类P-code程序
- 编译程序正常结東时
- PL/0语言编译系统调解释程序(类P-code虚拟机)
- 解释执行所生成的目标程序
- T形图表示编译程序涉及的三方面语言
- 源语言
- 目标语言
- 编译程序的书写语言(实现语言)
- 本书PL/0语言编译系统所有版本
- 类P-code虚拟机都采用与编写PL/0编译程序相同的语言(即C、 Pascal或Java)来仿真或解释实现
- 可阅读附录A源代码中名为Interpret的函数(或过程)理解类P-code虚拟机的解释实现过程。
- 对PL/0编译程序和类P-code虚拟机联编,就可生成目标平台上可执行的PL/0语言编译系统。
- 用高级语言实现的类P-code虚拟机是平台无关,PL/0语言编译系统可方便移植到任何目标平台
1.4.2 PL/0语言
- PL/0语言是Pascal子集
- 程序语言的语法描述常用扩展巴克斯范式(EBNF)描述
- 简洁和易读,适合编译程序的手工构造
- 后面介绍的PL/0编译程序手工构造过程都
- 基于这种EBNF形式描述
- <>:
- 尖括号括起来的中文字表示语法构造成分,或称语法单元
- 用尖括号括起来的英文字表示一类词法单元
- ::=
- 左部的语法单位由右部定义,“定义为”。
- |
- 表示“或”,即多选项
- {}
- 花括号括起来的成分可以重复0次到任意多次
- []
- 方括号括起来的成分为任选项,即出现一次或不出现
- 表1.1的EBNF观察到:
- PL-/0程序由分程序组成,
- 分程序由说明部分和语句组成。
- 说明部分
- 常量说明、变量说明和过程说明,由关键字 const、var procedure开头。
- 过程说明中可嵌套过程说明。
- 语句有赋值、条件、循环、过程调用、读、写和复合语句。
- 两种控制转移语句:if和while语句,if语句无else部分
- 复合语句是由begin和end括起来的语句序列
- I/O语句由保留字read和 write开始,
- read一次可为多个变量读入数据,
- write一次可输出多个算术表达式的值
- 表达式有
- 关系表达式和算术表达式两类:
- 控制转移语句中的测试条件是关系表达式,
- 其中使用了关系运算符;
- 算术表达式使用整型运算符。
- 关系表达式由两个算术表达式的比较组成,通过6个关系运算比较。
- 算术表达式由整型常数、整型变量以及常见的算术运算符组成。
- 算术表达式还可以出现在赋值、I/O语句
转载地址:https://cyj666.blog.csdn.net/article/details/103149558 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月07日 00时47分31秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
C#中ref和out的使用小结
2019-04-30
(C#)方法参数关键字:ref、out、params详解
2019-04-30
大话C#中in,out,ref的作用和区别
2019-04-30
IEnumerator和IEnumerable是怎样使用的
2019-04-30
类变量 索引器
2019-04-30
IEnumerable和IEnumerator 详解
2019-04-30
非泛型集合类以及对应的泛型集合类【dictionary】
2019-04-30
迭代器学习之一:使用IEnumerable和IEnumerator接口
2019-04-30
迭代器学习之二:数组的可枚举类型和枚举数的定义以及编译器的foreach工作原理
2019-04-30
迭代器学习之三:IEnumerable和IEnumerator的泛型结构
2019-04-30
迭代器学习之四:关于yield的深入了解
2019-04-30
ORA-02046: ORA-02046:分布式事务处理已经开始
2019-04-30
栈-堆-C#详解
2019-04-30
值类型-引用类型
2019-04-30
C# Random生成随机数字和字母
2019-04-30
c# 扩展方法奇思妙用基础篇四:Random 扩展
2019-04-30
创建和读写文件的一些简单方法
2019-04-30
XmlDocument操作xml文档
2019-04-30