编译原理学习教程之-引论(第一章)

1.引论

完成语言翻译工作的软件系统称为编译器(compiler)

1.1 语言处理器

一个编译器就是一个程序

解释器(interpreter)是另一种常见的语言处理器,它并不通过翻译的方式生成目标程序,从用户角度看,解释器直接利用用户提供的输入执行源程序中指定的操作。

链接器(linker)能够解决外部内存地址的问题
加载器(loader)把所有的可执行目标文件放到内存中

1.2 编译器结构

分析(analysis)把源程序分解为多个组成要素,并在这些要素之上加上语法结构

综合(syhthesis)根据中间表示和符号表中的信息哎构造用户期待的目标程序,分析部分一般称为编译器的前端(front end)而综合部分被称为后端(back end)

1.2.1 词法分析

编译器的第一个步骤称为词法分析(lexical analysis)或者扫描(scanning),读入组成源程序的字符流,并且将它们组织称为有意义的词素(element),对于词素,词法分析器产生如下形式的词法单元(token)

graph TD
  字符流 --> 词法分析器
  词法分析器 --> 符号流
  符号流  -->  语法分析
  语法分析  -->  语法树
  语法树  -->  中间代码生成器
  中间代码生成器 -->  中间表示形式
  中间表示形式  -->  机器无关代码优化器
  机器无关代码优化器 --> 中间表示形式
  中间表示形式 --> 代码生成器
  代码生成器 --> 目标机器语言
  目标机器语言 --> 机器相关代码优化器
  机器相关代码优化器 --> 目标机器语言
  -->
1.2.2 语法分析

语法分析使用由词法分析器生成的各个词法单元的第一个分量来创建树形的中间表示,该中间表示给出了词法分析产生的词法单元流的语法结构。

1.2.3 语义分析

语法分析(semantic analyzer)使用语法树和符号表中的信息来检查源程序是否和语言定义的语法一致。
语义分析的一个重要部分是类型检查。

1.2.4 中间代码生成

编译器可能在将源程序翻译成目标代码的过程中,翻译出一个或者多个中间表示。

1.2.5 代码优化

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

1.2.6 代码生成

代码生成器以源程序的中间表示形式作为输入,并把它映射到目标语言。代码生成的一个只管中间的方面是合理分配寄存器以存放变量。

1.2.7 符号表管理

符号表数据结构为每个变量名字创建了一个记录条目,记录的字段就是名字的各个属性,这个数据结构应该允许编译器迅速查找到每个名字的记录,并向记录中快速存放和获取记录中的数据。

1.2.8 多个步骤组合

多个步骤的活动可以被组合成一趟,每趟读取一个输入文件并产生一个输出文件。

1.2.9 编译器构造工具

常用的编译器构建工具包括:
1.语法分析器的生成器:可以根据一个程序设计语言的语法描述自动生成语法分析器;
2.扫描器的生成器:根据一个语言的语法单元的正则表达式描述生成词法分析器;
3.语法制导的翻译引擎:可以生成一组用于遍历分析书并生成中间代码的例程
4.代码生成器的生成器:依据一组关于如何把中间语言的每个运算翻译成目标机上的及其语言规则,生成一个代码生成器;
5.数据流分析引擎:帮助收集数据流信息,即程序中的值如何从一个部分传递到另一部分,数据流分析的代码优化的一个重要部分。
6.编译器构造工具集:提供可以用于构造编译器的不同阶段的例程的完整集合。

1.3 程序设计语言的发展历程

1.3.1 走向高级程序设计语言

第一代是及其语言,第二代是汇编语言,第三代是Fortan,Lisp,C ,C++,C#及Java这样的高级程序设计语言。
另一种语言分类方式把程序中指明如何完成一个计算任务的语言称为强制式(imperative)语言,把程序中指明要进行哪些计算的语言称为声明式(declarative)语言。

1.3.2 对编译器的影响

编译器必须能够正切翻译用源语言书写的所有程序,这样的程序集合通常是无穷的。

1.4构建一个编译器的相关科学

编译器必须接受所有遵循语言规范的源程序,源程序的集合是无穷的编译器所做的任何翻译工作都不能改变被编译源程序的含义。

1.4.1 编译器设计和实现中的建模

对编译器的研究主要是有关如何设计正确的数学模型和选择正确算法的研究。最基本的数学模型是有穷状态自动机和正则表达式。

1.4.2代码优化的科学

编译器优化必须满足下面的设计目标:
1.优化必须是正确的,也就是说,不能改变被编译程序的含义;
2.优化必须能够改变很多程序的性能;
3.优化所需的时间必须保持在合理的范围内;
4.所需要的工程方面的工作必须是可管理的。

第二个目标是编译器应该有效提高很多输入程序的性能;
第三个要使编译时间保持在较短的范围内;

1.5编译技术的应用

编译器设计并不只是关于编译器的,很多人用到了在学校里面研究编译器时学到的技术

1.5.1高级程序设计语言的实现

一个高级程序设计语言定义了一个编程抽象,程序要使用这个语言表达算法, 而编译器必须要把这个程序翻译成目标语言。
1.数据抽象
2.特性的继承

1.5.2 针对计算机体系结构的优化

所有的高性能西戎都利用了两种技术:并行(parallelism)和内存层次结构(memory hierarchy),并行可以出现在多个层次上:在指令层次上,多个运算可以被同时执行 ,在处理层次上,同一个应用的多个不同线程在不同的处理器上运行。
并行性
指令级的并行也显示地出现在指令集中,VLIW(Very Long Instruction Word,非常长指令字)机器用友可并行执行多个运算的指令。
内存层次结构

1.5.3 型计算机体系结构的设计

RISC
有关编译器如何影响计算机体系结构设计的最有名的例子之一是RISC(Reduced Instruction Set Computer,精简指令集计算机)在发明RISC之前,趋势是开发的指令集越来越复杂。以使得汇编编程变得更容易,这些体系结构称为CISC(Complex Instruction Set Computer,复杂指令集计算机)

专用体系结构

过去的30年中包括:数据流机器,向量机,VLIW(非常长指令字)机器,SIMD(单指令,多数据)处理器阵列,心动阵列(systolic array)共享内存的多处理器、分布式内存的多处理器

1.5.4程序翻译

二进制翻译

编译器技术可以把一个机器的二进制翻译成另一个机器的二进制代码,使得可以在一个机器上运行原来为另一个指令集编译的程序。二进制翻译也可以用来提供向后兼容性。

硬件合成
Verilog和VDHL(Very High-speed integrated circuit Hardware Description language,甚高速集成电路硬件描述语言).硬件设计通常是在寄存器传输层(Register Transfer Level,RTL)上描述的

数据查询解释器
数据库查询由包含了关系和布尔运算符的断言组成,它们可以被解释,也可以编译为代码,以便在一个数据库中搜索满足这个断言的记录

编译然后模拟
模拟是在很多科学和工程领域内使用的通用技术。

1.5.5 软件生产率工具

类型检查
类型检查是一种有效的,且被充分研究的技术,它可以用来检查一些错误,比如运算被作用于错误类型的对象上,或者传递一个过程的参数和该过程的泛型(signature)不匹配。

边界检查
相对于教高级的程序设计语言而言,用较低级语言编程更加容易犯错,

内管管理工具
垃圾收集机制是在效率和易编程及软件可靠性之间进行折中处理的另一个极好的例子。

1.6程序设计语言基础

1.6.1 静态和动态的区别

编译器能够对一个程序作出那些判定,如果一个语言使用的策略支持编译器静态决定某个问题,那么我们说这个语言使用了一个静态(static)策略,或者说这个问题可以在编译时(compile time)决定,另一方面,一个只允许在运行程序的时候作出决定的策略被称为动态策略(dynamic policy)。或者被认为需要在运行时刻(run time)作出决定。

大部分语言(C和Java)使用静态作用域

1.6.2 环境与状态

1.环境(environment)是从一个从名字到存储位置的映射,因为变量就是指内存位置,我们还可以换一种方法,把环境定义为从名字到变量的映射。
2.状态(state)是从一个内存位置到它们的值的映射。

1.6.3静态作用域和块结构

C语言的作用域规则是基于程序结构的,一个声明的作用 域又该声明在程序中出现的位置隐含地决定

1.6.4 显式访问控制

通过public private和protected这样的关键字的使用,像C++和Java这样的面向对象语言提供了对超累中的成员名字的显示访问控制。

声明
声明告诉我们事物的类型
定义
定义告诉我们事物的值

int i // 声明
i=0 // 定义(定值)
1.6.5 动态作用域

如果一个作用域策略依赖于一个或者多个只有在程序执行时刻才能知道的因素,那么它就是动态的。
静态作用域和动态作用域的类比
在通常的(块结构)静态作用域规则和通常的动态策略之间有一个有趣的关系,从某种意义上来说,动态规则处理时间的方式类似于静态作用域处理空间的方式,静态规则 让我们寻找声明位于最内层的,包含变量使用位置的单元(块)中;动态规则让我们寻找的声明位于最内层的,包含了变量使用时间的单元(过程调用)中。

1.6.6参数传递机制

值调用
在值调用(call by value)中,会对实在参数求值(如果它是表达式)或者拷贝(如果是它的变量)这些值被放在属于被调用过程的相应形式参数的内存位置上。值调用的效果是,被调用过程所做的所有有关形式参数的计算都局限于这个过程,相应的实在参数本身不会被改变

引用调用
在引用调用(call-by-reference)中,实在参数的地址作为相应的形式参数的值被传递给被调用者,在被调用者的代码中使用形式参数时,实现方法就是沿着这个指针找到调用者指明的内存位置,因此,改变形式参数看起来是改变了实在参数一样。
但是如果实在参数是一个表达式,那么在调用之前首先会对表达式求职,然后它的值被存放在一个该值自己的位置上 ,改变形式参数会改变这个位置上的值,但是对调用者的数据没有影响。

名调用
第三种机制-名调用,它要求被调用者的运行方式好像是用实在参数以字面方式替换了被调用者代码中的形式参数一样

1.6.7 别名

两个形式参数指向同一个位置,这样的变量被称为另一个变量的别名(alias),结果是,任意两个看起来从不同的形式参数中获得值的变量也可能变成对方的别名。

1.6.8
## 1.7 第一章总结
  • 语言处理器:一个集成的软件开发环境,其中包括很多种类的语言处理器,比如编译器,解释器,汇编器,连接器,加载器,调试器以及程序概要提取工具。
  • 编译器的步骤:一个编译器的运行需要一系列的步骤,每个步骤把源程序从一个中间表示转换成为另一个中间表示。
  • 机器语言和汇编语言:机器语言是第一代程序设计语言,然后是汇编语言。使用这些语言进行编程既费时,又容易出错。
  • 编译器设计中的建模:编译器设计是理论对时间有很大影响的领域之一,已知在编译器设计中有用的模型包括:自动机,文法,正表达式,树形结构和很多其他理论概念。
  • 代码优化:虽然代码不能真正达到最优化,但是提高代码效率的科学既复杂又非常重要。它是编译器技术研究的一个主要部分。
  • 高级语言:随着时间的流逝,程序设计语言担负了越来越多的原先由程序员负责的任务,比如内存管理,类型一致性检查或者代码的并发执行;
  • 编译器和计算机体系结构:编译器技术影响了计算机的体系结构,同时也受到体系结构发展的影响,体系结构中的很多现代创新都依赖于编译器能够从源程序中抽取出有效利用硬件的机会。
  • 软件生产率和软件安全性:使得编译器能够优化代码的技术同样能够用于多种不同的程序分析任务;
  • 作用域规则:一个x的声明的作用域是一段上下文,在此上下文中对x的使用指向这个声明,如果仅仅通过阅读某个语言的程序就可以确定其作用域,那么这个语言就使用了静态作用域,或者词法作用域,否则就使用了动态作用域。
  • 环境:名字和内存位置关联,然后再和值关联,这个情况可以使用环境和状态来描述,其中环境把名字映射为存储位置,而状态则把位置映射到它的值。
  • 块结构:允许语句块相互嵌套的语言称为块结构的语言,假设一个块中有一个x的声明D,而嵌套与这个块中的B中有一个对名字x的使用。如果在这两个块之间没有其他声明了x的块,那么这个x的使用位于D的作用域内。
  • 参数传递:参数可以通过值或者引用的方式从调用过程传递给被调用过程,通过值传递方式传递大型对象时,实际被传递的值是指向这些对象本身的引用。这样就变成了一个高效的引用;
  • 别名:当参数被以引用传递方式(高效地)传递时,两个形式参数可能会指向同一个对象,这会造成一个变量的修改改变另一个变量的值。
2.简单语法翻译器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值