编译原理_P1001

1  绝大部分软件使用高级程序设计语言来编写。用这些语言编写的软件必须经过编译器的编译,才能转换为可以在计算机上运行的机器代码。编译器所生成代码的正确性和质量会直接影响成千上万的软件。虽然大部分人不会参与设计编译器,但拥有编译相关知识会对他们的研究开发盛宴产生有益的影响。

2  引论

3  对编程语言的设计和实现有深刻的理解,对和编程语言有关的理论有所了解,对宏观上把握编程语言来说,起到一个点击作用,从软件功能看,编译器是一个很好的实例,所介绍的概念和技术引用到一般的软件设计中。

  翻译器(translator)、编译器(compiler)、解释器(interpreter)

3.1  编译器从逻辑上可以分层若干个阶段,每个阶段把源程序把一种表示变化成另一种解释

源程序----词法分析器----语法分析器----语义分析器----中间代码生成器----独立于机器的代码优化器----代码生成器----依赖于机器的代码优化器----目标机器代码

符号表

3.2  词法分析器

3.3  语法分析器

表达式的语法特征

3.4  语义分析器

3.5  中间代码生成器

3.6  代码优化器

3.7  代码生成器

3.8  解释器和编译器的区别

解释器不生成目标代码,而是直接执行源程序所指定的运算

3.9  BASIC年代的解释器

  功能:将高级语言的源程序翻译成一种中间语言程序,然后对中间语言程序进行解释执行,在那个年代,编译和解释两个功能是合在一起的程序中,改程序被称为解释器。

  Java年代的解释器

  上述两个功能在两个程序中,前一个叫做编译器,它把源程序翻译成一种叫做字节代码的中间语言程序,后一个叫做解释器,他对字节码程序进行解释执行。

 3.10  阶段分组(前端和后端,遍(扫描次数,写入外部存储器,C语言是两边扫描))

4.  编译器技术的应用

每一轮高级语言的出现都会刺激编译器优化的新研究,支持用户定义的聚合数据类型和高级控制流,面向对象的主要概念是数据抽象和性质集成,是的程序更加模块化易于维护,类型安全的语言:Java没有指针,也不允许指针算术,C语言不是类型安全语言,它用无用单元收集机制来自动的释放那些不在使用的变量占据的内存。Java设计来支持代码移植

 4.1  RISC和CISC

  RISC:精简指令集计算机 reduced instruction-set computer

  CISC:复杂指令集计算机 complex instruction-set computer

  https://blog.csdn.net/qq_29134495/article/details/51450812  

4.2  

  宏:Macro

  是一种批量处理的称谓。一般说来,宏是一种规则或模式,或称语法替换,用于说明某一特定的输入如何根据预定义的规则转换成对应的输出。这种替换在预编译时进行,称作宏展开。

5.  软件生产率工具

  在使用程序分析技术来提高软件生产效率的几个已有途径,如下:

  类型检查:类型检查是一种有效的,且被充分研究的技术,它可以用于捕捉程序中的不一致。对传递给一个过程的参数和该过程的范型(signature)不匹配。比如一个指针被赋予了NULL值,然后又被立即释放了,这个程序显然是错误的。

  边界检查:对于像高级的程序设计语言而言,用较低级语言编程更容易犯错。容易造成内存泄漏

  内存管理工具:像Java等语言,采用垃圾收集机制是在效率和易编译及软件可靠性之间进行折中处理的另一个几号的例子。

6.  程序设计语言基础

6.1  静态和动态的区别

  在为一个语言设计一个编译器时,我们所面对的最重要的问题之一就是编译器能够对一个过程做出那些判定。

  static policy静态策略:在编译时解决compile time

  dynamic policy动态策略:在运行时刻做出决定run time

  作用域(scope)

  作用域:分静态作用域(或者说叫词法作用域lexical scope)和动态作用域

6.2  环境与状态

  我们在套利程序设计语言时必须了解的另一个重要的区别是在程序运行时发生的改变是否会硬性数据元素的值,还是影响了对那个数据的名字的解释。

  环境(environment):从名字到存储位置的映射。也就是C语言的左值

  状态(state):一个从内存位置倒它们的值的映射,映射为他们相应的右值。

6.3  静态作用域和块结构

  作用域的控制如:public、private和protected等。

  名字、标识符和变量

  虽然术语“名字”、“变量”通常指的是同一事物,我们还是很小心的使用他们,以便区别编译时刻的名字和名字时刻所指的内存位置。

  标识符(identifier)是一个字符串,通常由数字和字母组成,用它来指向(标记)一个实体,比如一个数据对象、过程、类或者类型。

  过程、函数和方法

  函数通常指的是子程序,我们讨论一个可被调用的子程序时,我们通常把他们统称为过程,如果讨论像java这样的只有方法的语言时,我们使用函数这个属于。

  块:

  块是一种语句。块可以出现在其他类型的语句(比如赋值语句)所能够出现的任何地方。一个块包含了一个声明的序列,然后再跟着一个语句序列。这些声明和语句用一堆括号包围起来。

  注意,这个语法运行一个块嵌套在另一个块内。这类嵌套的特性成为块结构(block structure),C族语言都具有块结构,但不能在一个函数内部定义另一个函数。

6.4  显示访问控制

  类和结构为它们的成员引入了新的作用域。通过向public、private和protected这样的关键字的使用,像C++或Java这样的面相对象语言提供了对超类中的成员名字的显示访问控制。这些关键字通过限制访问来支持封装(encapsulation)。因此私有private名字被有意的限制了作用域,这个作用域仅仅包含了该类和“友类”相关的方法声明和定义,被保护的protected名字可以由子类访问,而公共的public名字可以从类外访问。

  声明和定义

  声明:告诉事物的类型

  定义:告诉他们的值

6.5  动态作用域

  从技术上来讲,如果一个作用域策略依赖于一个或多个只有在程序执行时刻才能知道的因素,他就是动态的。

  静态作用域和动态作用域的类比

  虽然可以有各种各样的静态或者动态作用域策略,在通常的静态作用域规则和通常的动态策略之间有一个有趣的关系。从某种意义上来说,动态规则处理时间的方法类似于静态作用域处理空间的方法。静态规则让我们寻找的声明位与最内层、包含变量使用位置的单元块中;而动态规则让我们寻找声明位于最内层的、包含了变量使用时间的单元中。

6.6  参数传递机制

  值调用(call-by-value):实参求值或拷贝,对应相应的内存位置上。

  引用调用(call-by-Reference):实参地址作为相应的形参值被传递调用。

  名调用:要求调用者的运行方式是用实参以字面方式替换了被调用者的代码中的形参一样。

6.7  别名(alias)

  任意两个看起来从两个不同的形参中获得值的变量也可能变成对方的别名。

6.8  前面的小结

  * 语言处理器:一个集成的软件开发环境(IDE),其中包括了很多种类的语言处理器,比如编译器、解释器、汇编器、连接器、加载器、调试器以及城乡概要提取工具。

  * 编译的步骤:一个编译器的运作需要一系列的步骤,把每个步骤的源程序从一个中间表(符号表)转换成另一个中间表示。

  * 机器语言和汇编语言:机器语言是第一代程序设计语言,然后是汇编语言。使用这些语言进行编程既费时,又容易出错。

  * 编译器设计中的建模:编译器设计是理论对实践有很大影响的领域之一。已知在编译器设计中有用的模型包括:自动机、文法、正则表达式、树型结构和很多其他理论概念。

  * 代码优化:虽然代码不能真正达到最优化,但是提高代码效率的科学即复杂又非常重要。他们编译技术研究的一个主要部分。

  * 高级语言:随着时间的流失,程序设计语言负担了越来越多的原先由程序员负责的任务,比如内存管理,类型一致性检查或diamante的并发执行。

  * 编译器和计算机体系结构:编译器技术影响了计算机的体系结构,同时也受到体系结构的发展的影响。体系结构中的很多现代创新都依赖于编译器能够从源程序中提取出有效利用的硬件能力的机会。

  * 软件生产率和软件安全性:使得编译器能够优化代码的技术同样能够用于多种不同的程序分析任务。这些任务既包括探测常见的程序错误,也包括发现程序可能受到已被黑发现的多种入侵方式的伤害。

  * 作用域规则:一个x的声明的作用域是一段上下文,在此上下文中对x的使用指向这个声明。如果仅仅通过阅读某个语言的程序就可以确定其作用域,那么这个语言就使用了静态作用域,或者词法作用域。否则这个语言就使用了动态作用域。

  * 环境:名字和内存位置关联,然后再和值相关联。这种情况可以使用环境和状态来描述。其中环境把名字映射成为存储位置,而状态则把位置映射到它的值。

  * 块结构:运行语句块相互嵌套的语言成为块结构的语言。假设一个块中有一个x的声明D,而嵌套与整个块中的块B中有一个对应名字x的使用。如果在两个块之间没有其他声明了x的块,那么这个x的使用位于D的作用域内。

  * 参数传递:参数可以通过值或引用的方法从调用过程传递给被调用过程。当通过值传递方式传递大型对象时,实际被传递的值是指向这些对象本身的引用。这样就变成了一个高效的引用调用。

  * 别名:当参数被以引用传递方式传递时,两个形参可能会指向同一个对象。这会造成一个变量的修改改变了另一个变量的值。

7.  由于高级语言的不断进步,进而书写和文法的方式越来越接近人类正常的语言逻辑体系,因此提出了作用域、动态静态、块、类等概念去推动高级语言的语言逻辑,因此对应的编译工作也变得不是想早些时候的那样简单。因此掌握这些高级语言的逻辑体系是套利编译这些工作的先决条件,编译的工作归根结底是要编程一串二进制代码,或者一串机器代码,让计算机从最简单的加减乘除语言提高处理更复杂的问题,但是还是归根结底需要编译成二进制代码,效率、编译机器代码的准确度是衡量编译工作的标准。

8.  一个简单的语法指导翻译器

  按照前面所述的词法分析---语法分析器--语义分析器等进行一个编译技术的总体介绍。

  以一个三地址代码(中间表示形式)进行介绍。

  总体例子如下:一个将要被翻译的代码片段

8.1  引言

  编译器在分析阶段(前段)把一个源程序划分为各个组成部分,并生成源程序的内部表示形式,这种内部表示成为中间代码。

  分析阶段的工作是围绕着待编译语言的“语法”展开的。BNF(Backus-Naur范式)

  中间程序片段对应的经过简化的中间代码表示

  语法制导翻译(syntax-directed translation)技术。

8.2  “上下文无关文法”

  四个元素组成:

  * 一个终结符号集合,他们有时也称为“词法单元”。终结符号是该文法所定义的语言的基本符号集合。

  * 一个非终结符号集合,它们有时也称为“语法变量”。每个非终结符号表示一个终结符号串的集合。

8.3  二义性

  一个文法可能有多棵语法分析树能够生成同一个给定的终结符号串,这样的文法称为二义性(ambiguous)。要证明一个文法具有二义性,我们只需要找到一个终结符号串,说明它是两棵以上语法分析树的结果。

8.4  运算符的优先级

  算术表达式的文法可以根据表示运算符结核性和优先级的表格来构建。

  左结合:+ -

  左结合:* /

  中间表达式文法的推广

  我们可以将因子factor理解成不能被任何运算符分开的表达式。“不能分开”的意思是说当我们在任意因子的任意一边放置一个运算符,都不会导致整个因子的任何部分分离出来,成为整个运算符的运算分量。当然,因子本身作为一个整体可以成为该运算符的一个运算分量。如果这个因子是由一个括号括起来的表达式,那么括号将起到保护其不被分开的作用。如果因子就是运算分量,那么它当然不能被分开。

  一个(不是因子)的项(term)是一个可能被高优先级的运算符*和/分开,但不能被低优先级运算符分开的表达式。一个不是因子也不是项的表达式可能被任何一个运算符分开。

  我们可以把这种思想推广到具有任意n层优先级的情况。我们需要n+1个非终结符号。通常非终结符产生式有些产生式体表示了该优先级的运算符的应用;另有一个产生式体只包含了代表更高一层优先级的非终结符号。

8.5  语法制导翻译是通过向一个文法的产生式附加一些规则或程序片段而得到。比如考虑由如下产生式生成的表达式expr:  

  expr → expr1 + term

这里,expr是由两个子表达式expr1和term的和。

  翻译:expr

  翻译:term

  处理:+

8.6  两个语法制导翻译相关的概念:

  属性(attribute):属性表示与某个程序构造相关的任意的量。属性可以是多种多样的,比如表达式的数据类型、生成的代码中的指令数目或为某个构造生成的代码中第一条指令的位置等等都是属性的例子。因为我们用文法符号(终结符号或非终结符号)来表示程序构造,所以我们将属性的概念从程序构造扩展到表示这些构造的文法符号上。

  翻译方案(translation scheme):翻译方案是一种将程序片段附加到一个文法的各个产生式上的表示法。当在语法分析过程中使用一个产生式时,相应的程序片段就会执行。这些程序片段的执行效果按照语法分析过程的顺序组合起来,得到的结果就是这次分析/综合过程处理源程序得到的翻译结果。它将用于把中间表达式翻译成后缀表达式,还会用于表达式求值,并用来构建一些程序构造的抽象语法书。

8.7  树的遍历

  树的遍历将用于描述属性的求值过程,以及描述一个翻译方案中的各个代码片段的执行过程。一个树的遍历Traversal从根节点开始,并按照某个顺序访问树的各个节点。

  一次深度优先(depth-first)遍历从根节点开始,递归的按照任意顺序访问各个结点的子节点,并不一定按照从左向右的顺序遍历。之所以称为深度优先,是因为这种遍历总是尽可能的访问一个结点的尚未被访问的子节点,因为它总是尽可能快的访问离根结点最远的结点(即最深的结点)。

8.8  前序遍历和后序遍历

  前序遍历和后序遍历是深度优先遍历的两种重要的特例。在这两种遍历中,我们都是从左到右递归的访问每个结点的子结点。

  我们经常遍历一棵树,并在各个结点上执行某些特定动作。如果动作在我们第一次访问一个结点时被执行,那么我们将这种遍历称为前序遍历(preorder travelsal)。类似的,如果动作在我们离开最后一个结点前被执行,则称这种遍历为后序遍历(postorder traversal)。前序遍历和后序遍历根据一个结点的动作执行时间来定义这些结点的相应次序。一棵以结点N为根的子树的前序排列由N,跟上它从左到右的每棵子树(如果存在)的前序排列组成。而一棵以结点N为根的子树的后序排列则由N的从左到右的每棵子树的后序排序,再跟上N自身组成。

8.9  符号表

  symbol table是一种编译器用于保存有关源程序构造的各种信息的数据结构。这些信息在编译器的分析阶段被逐步收集并放入符号表,它们在总和阶段用于生成目标代码。

原文地址:https://www.cnblogs.com/noah0532/p/8972786.html