编译原理 订阅
编译原理是计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。 编译原理是计算机专业设置的一门重要的专业课程。编译原理课程是计算机相关专业学生的必修课程和高等学校培养计算机专业人才的基础及核心课程,同时也是计算机专业课程中最难及最挑战学习能力的课程之一。编译原理课程内容主要是原理性质,高度抽象 [1]  。 展开全文
编译原理是计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。 编译原理是计算机专业设置的一门重要的专业课程。编译原理课程是计算机相关专业学生的必修课程和高等学校培养计算机专业人才的基础及核心课程,同时也是计算机专业课程中最难及最挑战学习能力的课程之一。编译原理课程内容主要是原理性质,高度抽象 [1]  。
信息
外文名
Compilers: Principles, Techniques, and Tools [1]
领    域
计算机专业的一门重要专业课 [1]
中文名
编译原理 [1]
编译原理基本概念
编译原理即是对高级程序语言进行翻译的一门科学技术, 我们都知道计算机程序由程序语言编写而成, 在早期计算机程序语言发展较为缓慢, 因为计算机存储的数据和执行的程序都是由0、1代码组合而成的, 那么在早期程序员编写计算机程序时必须十分了解计算机的底层指令代码通过将这些微程序指令组合排列从而完成一个特定功能的程序, 这就对程序员的要求非常高了。人们一直在研究如何如何高效的开发计算机程序, 使编程的门槛降低。 [2] 
收起全文
精华内容
下载资源
问答
  • 编译原理

    千次阅读 2016-07-07 16:49:22
    编译原理 编辑词条 B 添加义项  ? 编译原理是计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成...

    编译原理 编辑词条

    编译原理计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。 编译原理是计算机专业设置的一门重要的专业课程。虽然只有少数人从事编译方面的工作,但是这门课在理论、技术、方法上都对学生提供了系统而有效的训练,有利于提高软件人员的素质和能力。 目前各个大学使用的教材机械工业出版社、国防工业出版社出版的《编译原理》。

    基本信息

    • 书名

      编译原理

    • 装帧

      平装

     
    • 开本

      16

    • 页数

      542

    编译原理计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。 编译原理是计算机专业设置的一门重要的专业课程。虽然只有少数人从事编译方面的工作,但是这门课在理论、技术、方法上都对学生提供了系统而有效的训练,有利于提高软件人员的素质和能力。 目前各个大学使用的教材机械工业出版社、国防工业出版社出版的《编译原理》。

    折叠编辑本段基本概念

    编译器 是将汇编或高级计算机语言翻译为二进制机器语言代码的计算机程序。编译器将源程序(source language) 编写的程序作为输入,翻译产生目标语言(target language )机器代码的等价程序。通常地,源程序为高级语言(high-level language ),像C或C + +、汉语语言程序等,而目标则是机器语言的目标代码 (object code,有时也称作机器代码(machine code )),也就是可以在计算机硬件中运行的机器代码软件程序。这一过程可以表示为:

    源程序→编译器 →目标机器代码程序

    折叠编辑本段编译原理课程

    这门课程关注的是编译器方面的产生原理和技术问题,似乎和计算机的基础领域不沾边,可是编译原理却一直作为大学本科的 必修课程,同时也成为了研究生入学考试的必考内容。编译原理及技术从本质上来讲就是一个算法问题而已,当然由于这个问题十分复杂,其解决算法也相对复杂。 我们学的数据结构与算法分析也是讲算法的,不过讲的基础算法,换句话说讲的是算法导论,而编译原理这门课程讲的就是比较专注解决一种的算法了。在20世纪 50年代,编译器的编写一直被认为是十分困难的事情,第一Fortran的编译器据说花了18年的时间才完成。在人们尝试编写编译器的同时,诞生了许多跟 编译相关的理论和技术,而这些理论和技术比一个实际的编译器本身价值更大。就犹如数学家们在解决著名的哥德巴赫猜想一样,虽然没有最终解决问题,但是其间 诞生不少名著的相关数论。

    折叠编辑本段发展历程

    在20世纪40年代,由于冯·诺伊曼在存储-程序编译原理实验程序编译原理实验程序计算机方面的先锋作用,编写一串代码或程序已成必要,这样计算机就可以执行所需的计算。开始时,这些程序都是用机器语言 (machine language )编写的。机器语言就是表示机器实际操作的数字代码,例如:

    C7 06 0000 0002 表示在IBM PC 上使用的Intel 8x86处理器将数字2移至地址0 0 0 0 (16进制)的指令。

    但编写这样的代码是十分费时和乏味的,这种代码形式很快就被汇编语言(assembly language )代替了。在汇编语言中,都是以符号形式给出指令和存储地址的。例如,汇编语言指令 MOV X,2 就与前面的机器指令等价(假设符号存储地址X是0 0 0 0 )。汇编程序(assembler )将汇编语言的符号代码和存储地址翻译成与机器语言相对应的数字代码。

    汇编语言大大提高了编程的速度和准确度,人们至今仍在使用着它,在编码需要极快的速度和极高的简洁程度时尤为如此。但是,汇编语言也有许多缺点:编写起来也不容易,阅读和理解很难;而且汇编语言的编写严格依赖于特定的机器,所以为一台计算机编写的代码在应用于另一台计算机时必须完全重写。

    发展编程技术的下一个重要步骤就是以一个更类似于数学定义或自然语言的简洁形式来编写程序的操作,它应与任何机器都无关,而且也可由一个程序翻译为可执行的代码。例如,前面的汇编语言代码可以写成一个简洁的与机器无关的形式 x = 2。

    在1954年至1957年期间,IBM的John Backus带领的一个研究小组对FORTRAN语言及其编译器的开发,使得上面的担忧不必要了。但是,由于当时处理中所涉及到的大多数程序设计语言的翻译并不为人所掌握,所以这个项目的成功也伴随着巨大的辛劳。几乎与此同时,人们也在开发着第一个编译器, Noam Chomsky开始了他的自然语言结构的研究。他的发现最终使得编译器结构异常简单,甚至还带有了一些自动化。Chomsky的研究导致了根据语言文法(grammar ,指定其结构的规则)的难易程度以及识别它们所需的算法来为语言分类。正如现在所称的-与乔姆斯基分类结构(Chomsky hierarchy )一样-包括了文法的4个层次:0型、1型、2型和3型文法,且其中的每一个都是其前者的专门化。2型(或上下文无关文法(context-free grammar ))被证明是程序设计语言中最有用的,而且今天它已代表着程序设计语言结构的标准方式。

    分析问题( parsing problem ,用于限定上下文无关语言的识别的有效算法)的研究是在20世纪60年代和70年代,它相当完善地解决了这一问题, 现在它已是编译理论的一个标准部分。它们与乔姆斯基的3型文法相对应。对它们的研究与乔姆斯基的研究几乎同时开始,并且引出了表示程序设计语言的单词(或称为记号)的符号方式。

    人们接着又深化了生成有效的目标代码的方法,这就是最初的编译器,它们被一直使用至今。人们通常将其误称为优化技术(optimization technique ),但因其从未真正地得到过被优化了的目标代码而仅仅改进了它的有效性,因此实际上应称作代码改进技术(code improvement technique )。

    这些程序最初被称为编译程序-编译器,但更确切地应称为分析程序生成器 (parser generator ),这是因为它们仅仅能够自动处理编译的一部分。这些程序中最著名的是 Yacc (yet another compiler- compiler),它是由Steve Johnson在1975年为Unix系统编写的。

    类似地,有穷自动机的研究也发展了另一种称为扫描程序生成器 (scanner generator )的工具,Lex (与Yacc同时,由Mike Lesk为Unix系统开发的)是这其中的佼佼者。在20世纪70年代后期和80年代早期,大量的项目都关注于编译器其他部分的生成自动化,这其中就包括代码生成。这些尝试并未取得多少成功,这大概是因为操作太复杂而人们又对其不甚了解。

    编译器设计最近的发展包括:首先,编译器包括了更为复杂的算法的应用程序,它用于推断或简化程序中的信息;这又与更为复杂的程序设计语言(可允许此类分析)的发展结合在一起。其中典型的有用于函数语言编译的Hindle y - Milner类型检查的统一算法。

    其次,编译器已越来越成为基于窗口的交互开发环境(interactive development environment,IDE )的一部 分,它包括了编辑器、链接程序、调试程序以及项目管理程序。这样的IDE的标准并没有多少, 但是已沿着这一方向对标准的窗口环境进行开发了。

    折叠编辑本段相关程序

    折叠解释程序

    解释程序(interpreter):解释程序是如同编译器的一种语言翻译程序。它与编译器的不同之处在于:它立即执行源程序而不是生成在翻译完成之后才执行的目标代码。从原理上讲,任何程序设计语言都可被解释或被编译,但是根据所使用的语言和翻译情况,很可能会选用解释程序而不用编译器。例如, 我们经常解释BASIC语言而不是去编译它。类似地,诸如LISP 的函数语言也常常是被解释的。

    解释程序也经常用于教育和软件的开发,此处的程序很有可能被翻译若干次。而另一方面,当执行的速度是最为重要的因素时就使用编译器,这是因为被编译的目标代码比被解释的源代码要快得多,有时要快10倍或更多。但是,解释程序具有许多与编译器共享的操作,而两者之间也有一些混合之处。

    折叠汇编程序

    汇编程序(assembler):汇编程序是用于特定计算机上的汇编语言的翻译程序。正如前面所提到的,汇编语言是计算机的机器语言的符号形式,它极易翻译。有时,编译器会生成汇编语言以作为其目标语言, 然后再由一个汇编程序将它翻译成目标代码。

    折叠连接程序

    连接程序(linker):编译器和汇编程序都经常依赖于连接程序,它将分别在不同的目标文件中编译或汇编的代码收集到一个可直接执行的文件中。在这种情况下,目标代码,即还未被连接的机器代码,与可执行的机器代码之间就有了区别。连接程序还连接目标程序和用于标准库函数的代码,以及连接目标程序和由计算机的操作系统提供的资源(例如,存储分配程序及输入与输出设备)。连接程序现在正在完成编译器最早的一个主要活动(这也是"编译"一词的用法, 即通过收集不同的来源来构造)。连接过程对操作系统和处理器有极大的依赖性。

    折叠装入程序

    装入程序(loader):编译器、汇编程序或连接程序生成的代码经常还不完全适用或不能执行,但是它们的主要存储器访问却可以在存储器的任何位置中且与一个不确定的起始位置相关。这样的代码被称为是可重定位的(relocatable ),而装入程序可处理所有的与指定的基地址或起始地址有关的可重定位的地址。装入程序使得可执行代码更加灵活,但是装入处理通常是在后台(作为操作环境的一部分)或与连接相联合时才发生,装入程序极少会是实际的独立程序。

    折叠预处理器

    预处理器(preprocessor ):预处理器是在真正的翻译开始之前由编译器调用的独立程序。预处理器可以删除注释、包含其他文件以及执行宏(宏macro是一段重复文字的简短描写)替代。预处理器可由语言(如 C )要求或以后作为提供额外功能(诸如为FORTRAN提供Ratfor预处理器)的附加软件。

    折叠编辑器

    编辑器(editor):编译器通常接受由任何生成标准文件(例如ASCII文件)的编辑器编写的源程序。现在, 编译器已与另一个编辑器和其他程序捆绑进一个交互的开发环境-IDE中。此时,尽管编辑器仍然生成标准文件,但会转向正被讨论的程序设计语言的格式或结构。这样的编辑器称为基于结构的(structure based ),且它早已包括了编译器的某些操作;因此,程序员就会在程序的编写时而不是在编译时就得知错误了。从编辑器中也可调用编译器以及与它共用的程序,这样程序员无需离开编辑器就可执行程序

    折叠调试程序

    调试程序(debugger ):调试程序是可在被编译了的程序中判定执行错误的程序,它也经常与编译器一起放在IDE 中。运行一个带有调试程序的程序与直接执行不同,这是因为调试程序保存着所有的或大多数源代码信息(诸如行数、变量名和过程)。它还可以在预先指定的位置(称为断点(breakpoint )) 暂停执行,并提供有关已调用的函数以及变量的当前值的信息。为了执行这些函数,编译器必须为调试程序提供恰当的符号信息,而这有时却相当困难,尤其是在一个要优化目标代码的编译器中。因此,调试又变成了一个编译问题。

    折叠描述器

    描述器(profiler):描述器是在执行中搜集目标程序行为统计的程序。程序员特别感兴趣的统计是每一个过程的调用次数和每一个过程执行时间所占的百分比。这样的统计对于帮助程序员提高程序的执行速度极为有用。有时编译器也甚至无需程序员的干涉就可利用描述器的输出来自动改进目标代码。

    折叠项目管理程序

    项目管理程序(project manager):软件项目通常大到需要由一组程序员来完成,这时对那些由不同人员操作的文件进行整理就非常重要了,而这正是项目管理程序的任务。例如,项目管理程序应将由不同的程序员制作的文件的各个独立版本整理在一起,它还应保存一组文件的更改历史,这样就能维持一个正在开发的程序的连贯版本了(这对那些由单个程序员管理的项目也很有用)。项目管理程序的编写可与语言无关,但当其与编译器捆绑在一起时,它就可以保持有关特定的编译器和建立一个完整的可执行程序的链接程序操作的信息。在Unix系统中有两个流行的项目管理程序:sccs (source code control system )和rcs (revision control system )。

    折叠编辑本段步骤

    编译器内部包括了许多步骤或称为阶段源代码(phase),它们执行不同的逻辑操作。将这些阶段设想为编译器中一个个单独的片断是很有用的, 尽管在应用中它们是经常组合在一起的,但它们扫描程序确实是作为单独的代码操作来编写的。

    折叠扫描程序

    扫描程序(scanner):在这个阶段编译器实际阅读源程序(通常以分析程序字符流的形式表示)。扫描程序执行词法分析注释树符号表 (Lexical analysis ):它将字符序列收集到称作记号错误处 (token )的有意义单元中,记号同自然语言,如英源代码理器语中的字词相似。因此可以认为扫描程序执行与优化程序拼写相似的任务。中间代码例如在下面的代码行(它可以是C程序的一部分)中:代码生成器 a [index] = 4 + 2 这个代码包括了1 2个非空字符,但只有 8个目标代码记号:a 标识符目标代码优化程序 [ 左括号 i n d e x 标识符 ] 右括号 = 赋值目标代码 4 数字编译器的阶段 + 加号 2 数字 每一个记号均由一个或多个字符组成,在进一步处理之前它已被收集在一个单元中。扫描程序还可完成与识别记号一起执行的其他操作。例如,它可将标识符输入到符号表中, 将文字(litral)输入到文字表中(文字包括诸如3 . 1415926535的数字常量,以及诸如"Hello,world ! "的引用字符串)。

    折叠语法分析

    语法分析(parser ):语法分析程序从扫描程序中获取记号形式的源代码,并完成定义程序结构的语法分析 (syntax analysis ),这与自然语言中句子的语法分析类似。语法分析定义了程序的结构元素及其关系。通常将语法分析的结果表示为分析树(parse tree)或语法树(syntax tree)。例如,还是那行C代码,它表示一个称为表达式的结构元素,该表达式是一个由左边为下标表达式、右边为整型表达式的赋值表达式组成。这个结构可按下面的形式表示为一个分析树:请注意,分析树的内部节点均由其表示的结构名标示出,而分析树的叶子则表示输入中的记号序列(结构名以不同字体表示以示与记号的区别)。分析树对于显示程序的语法或程序元素很有帮助,但是对于表示该结构却显得力不从心了。分析程序更趋向于生成语法树,语法树是分析树中所含信息的浓缩(有时因为语法树表示从分析树中的进一步抽取,所以也被称为抽象的语法树(abstract syntax tree ))。下面是一个C赋值语句的抽象语法树的例子:请注意,在语法树中,许多节点(包括记号节点在内)已经消失。例如,如果知道表达式是一个下标运算,则不再需要用括号"["和"]"来表示该操作是在原始输入中。

    折叠语义分析

    语义分析(semantic analyzer ):程序的语义就是它的"意思",它与语法或结构不同。程序的语义确定程序的运行,但是大多数的程序设计语言都具有在执行之前被确定而不易由语法表示和由分析程序分析的特征。这些特征被称作静态语义(static semantic),而语义分析程序的任务就是分析这样的语义(程序的"动态"语义具有只有在程序执行时才能确定的特性,由于编译器不能执行程序,所以它不能由编译器来确定)。一般的程序设计语言的典型静态语义包括声明和类型检查。由语义分析程序计算的额外信息(诸如数据类型)被称为属性(attribute),它们通常是作为注释或"装 饰"增加到树中(还可将属性添加到符号表中)。在正运行的C表达式 a [index] = 4 + 2 中,该行分析之前收集的典型类型信息可能是:a是一个整型值的数组,它带有来自整型子范围的下标;index则是一个整型变量。接着,语义分析程序将用所有的子表达式类型来标注语法树,并检查赋值是否使这些类型有意义了,如若没有,则声明一个类型匹配错误。在上例中, 所有的类型均有意义,有关语法树的语义分析结果可用以下注释了的树来表示。

    折叠优化程序

    优化程序(source code optimizer):编译器通常包括许多代码改进或优化步骤。绝大多数最早的优化步骤是在语义分析之后完 成的,而此时代码改进可能只依赖于源代码。这种可能性是通过将这一操作提供为编译过程中的单独阶段指出的。每个编译器不论在已完成的优化种类方面还是在优化阶段的定位中都有很大的差异。在上例中,我们包括了一个源代码层次的优化机会,也就是:表达式4 + 2可由编译器计算先得到结果6 (这种优化称为常量合并(constant folding ))。当然,还会有更复杂的情况。还是在上例中,通过将根节点右面的子树合并为它的常量值,这个优化就可以直接在(注释)语法树上完成:尽管许多优化可以直接在树上完成,但是在很多情况下,优化接近于汇编代码线性化形式的树更为简便。这样节点的变形有许多,但是三元式代码(three-address code )(之所以这样称呼是因为它在存储器中包含了3个(或3个以上)位置的地址)却是标准选择。另一个常见的选 择是P -代码(P - code ),它常用于Pascal编译器中。在前面的例子中,原先的C表达式的三元式代码应是:t = 4 + 2 a [ index] = t (请注意,这里利用了一个额外的临时变量t 存放加法的中间值)。这样,优化程序就将这个代码改进为两步。首先计算加法的结果:t = 6 a [index] = t 接着,将t替换为该值以得到三元语句 a [index] = 6 ,指出源代码优化程序可能通过将其输出称为中间代码(intermediate code )来使用三元式代码。中间代码一直是指一种位于源代码和目标代码(例如三元式代码或类似的线性表示)之间的代码表示形式。但是,我们可以更概括地认为它是编译器使用的源代码的任何一个内部表示。此时,也可将语法树称作中间代码,源代码优化程序则确实能继续在其输出中使用这个表示。有时,这个中间代码也称作中间表示(intermediate representation,IR)。

    折叠代码生成

    代码生成(code generator):代码生成器得到中间代码(IR),并生成目标机器的代码。正是在编译的这个阶段中,目标机器的特性成为了主要因素。当它存在于目标机器时,使用指令不仅是必须的而且数据的形式表示也起着重要的作用。例如,整型数据类型的变量和浮点数据类型的变量在存储器中所占的字节数或字数也很重要。在上面的示例中,现在必须决定怎样存储整型数来为数组索引生成代码。例如,下面是所给表达式的一个可能的样本代码序列(在假设的汇编语言中):

    M O V R0,index ;;

    value of index -> R0 M U L R0,2 ;;

    double value in R0 M O V R1,&a ;;

    address of a -> R1 A D D R1,R0 ;;

    add R0 to R1 M O V *R1,6 ;;

    constant 6 -> address in R1

    在以上代码中,为编址模式使用了一个类似C的协定,因此& a是a的地址(也就是数组的基地址),* R1则意味着间接寄存器地址(因此最后一条指令将值6存放在R1包含的地址中)。这个代码还假设机器执行字节编址,并且整型数占据存储器的两个字节(所以在第2条指令中用2作为乘数)。

    折叠目标代码

    目标代码(target code optimizer ):在这个阶段中,编译器尝试着改进由代码生成器生成的目标代码。这种改进包括选择编址模式以提高性能、将速度慢的指令更换成速度快的,以及删除多余的操作。在上面给出的样本目标代码中,还可以做许多更改:在第2条指令中,利用移位指令替代乘法(通常地,乘法很费时间),还可以使用更有效的编址模式(例如用索引地址来执行数组 存储)。使用了这两种优化后,目标代码就变成:

    MOV R0,index ;;

    value of index -> R0 SHL R0 ;;

    double value in R0 MOV &a[R0],6 ;;

    constant 6 -> address a + R0

    到这里就是编译原理的简要描述,但还应特别强调编译器在其结构细节上差别很大。

    折叠编辑本段数据结构

    编译原理一直是计算机学习的必修课.

    当然,由编译器的阶段使用的算法与支持这些阶段的数据结构之间的交互是非常强大的。编译器的编写者尽可能有效实施这些方法且不引起复杂性。理想的情况是:与程序大小成线性比例的时间内编译器,换言之就是,在0 ( n )时间内,n是程序大小的度量(通常是字符数)。本节将讲述一些主要的数据结构,它们是其操作部分阶段所需要的,并用来在阶段中交流信息。

    折叠记号

    记号(token):当扫描程序将字符收集到一个记号中时,它通常是以符号表示这个记号;这也就是说,作为一个枚举数据类型的值来表示源程序的记号集。有时还必须保留字符串本身或由此派生出的其他信息(例如:与标识符记号相关的名字或数字记号值)。在大多数语言中,扫描程序一次只需要生成一个记号(这称为单符号先行(single symbol lookahead))。在这种情况下,可以用全程变量放置记号信息;而在别的情况(最为明显的是FORTRAN)下,则可能会需要一个记号数组。

    折叠语法树

    语法树(syntax tree):如果分析程序确实生成了语法树,它的构造通常为基于指针的标准结构,在进行分析时动态分配该结构,则整棵树可作为一个指向根节点的单个变量保存。结构中的每一个节点都是一个记录,它的域表示由分析程序和之后的语义分析程序收集的信息。例如,一个表达式的数据类型可作为表达式的语法树节点中的域。有时为了节省空间,这些域也是动态分配或存放在诸如符号表的其他数据结构中,这样就可以有选择地进行分配和释放。实际上,根据它所表示的语言结构的类型(例如:表达式节点对于语句节点或声明节点都有不同的要求),每一个语法树节点本身都可能要求存储不同的属性。在这种情况下,可由不同的记录表示语法树中的每个节点,每个节点类型只包含与本身相关的信息。

    折叠符号表

    符号表(symbol table):这个数据结构中的信息与标识符有关:函数、变量、常量以及数据类型。符号表几乎与编译器的所有阶段交互:扫描程序、分析程序或将标识符输入到表格中的语义分析程序;语义分析程序将增加数据类型和其他信息;优化阶段和代码生成阶段也将利用由符号表提供的信息选 出恰当的代码。因为对符号表的访问如此频繁,所以插入、删除和访问操作都必须比常规操作更有效。尽管可以使用各种树的结构,但杂凑表却是达到这一要求的标准数据结构。有时在一个列表或栈中可使用若干个表格。

    折叠常数表

    常数表(literal table):常数表的功能是存放在程序中用到的常量和字符串,因此快速插入和查找在常数表中也十分重要。但是,在其中却无需删除,这是因为它的数据全程应用于程序而且常量或字符串在该表中只出现一次。通过允许重复使用常量和字符串,常数表对于缩小程序在存储器中的大小显得非常重要。在代码生成器中也需要常数表来构造用于常数和在目标代码文件中输入数据定义的符号地址。

    折叠中间代码

    中间代码(intermediate code):根据中间代码的类型(例如三元式代码和P -代码)和优化的类型,该代码可以是文本串的数组、临时文本文件或是结构的连接列表。对于进行复杂优化的编译器,应特别注意选择允许简单重组的表示。

    折叠临时文件

    临时文件(temporary file):计算机过去一直未能在编译器时将整个程序保留在存储器中。这一问题已经通过使用临时文件来保存翻译时中间步骤的结果或通过"匆忙地"编译(也就是只保留源程序早期部分的足够信息用以处理翻译)解决了。存储器的限制现在也只是一个小问题了,现在可以将整个编译单元放在存储器之中,特别是在可以分别编译的语言中时。但是偶尔还是会发现需要在某些运行步骤中生成中间文件。其中典型的是代码生成时需要反填(backpatch)地址。例如,当翻译如下的条件语句时 if x = 0 then ... else ... 在知道else部分代码的位置之前必须由文本跳到else部分:

    CMP X,0 JNE NEXT ;;

    location of NEXT not yet known < code for then-part > NEXT : < code for else-part >

    通常,必须为NEXT的值留出一个空格,一旦知道该值后就会将该空格填上,利用临时文件可以很容易地做到这一点。

    如果想利用上面的编译原理开发一套属于自己的编程语言,或者想在一个产品中嵌入编程语言,可以参考zengl开源网开发的zengl编程语言,该编程语言为国人使用C语言开发,里面包含两个部分,一个是编译器,一个是解释执行中间代码的虚拟机。编译器包含了词法扫描,语法分析,中间代码输出等,虚拟机则类似JAVA一样解释执行中间代码。作者将所有的版本都公布出来,好让读者可以由浅入深的做研究,并且为了证明该编程语言的实用性,还结合SDL游戏开发库开发了一款图形界面和命令行界面的21点扑克小游戏 。

    zengl编程语言目前适用平台为windows和linux (最开始在Linux下使用gcc开发,后来移植到windows平台)

    折叠编辑本段其他问题

    可从许多不同的角度来观察编译器的结构,还有其他一些可能的观点:编译器的物理结构、操作的顺序等等。由于编译器的结构对其可靠性、有效性、可用性以及可维护性都有很大的影响,所以编译器的编写者应熟悉尽可能多的有关编译器结构的观点。

    折叠分析和综合

    在这个观点中,已将分析源程序以计算其特性的编译器操作归为编译器的分析(analysis) 部分,而将生成翻译代码时所涉及到的操作称作编译器的综合(synthesis )部分。当然,词法分析、语法分析和语义分析均属于分析部分,而代码生成却是综合部分。在优化步骤中,分析和综合都有。分析正趋向于易懂和更具有数学性,而综合则要求更深的专业技术。因此,将分析步骤和综合步骤两者区分开来以便发生变化时互不影响是很有用的。

    折叠前端和后端

    本观点认为,将编译器分成了只依赖于源语言(前端(front end ))的操作和只依赖于目标语言(后端(back end ))的操作两部分。这与将其分成分析和综合两部分是类似的:扫描程序、分析程序和语义分析程序是前端,代码生成器是后端。但是一些优化分析可以依赖于目标语言,这样就是属于后端了,然而中间代码的综合却经常与目标语言无关,因此也就属于前端了。在理想情况下,编译器被严格地分成这两部分,而中间表示则作为其间的交流媒介。这一结构对于编译器的可移植性(portability)十分重要,此时设计的编译器既能改变源代码(它涉及到重写前端),又能改变目标代码(它还涉及到重写后端)。在实际中,这是很难 做到的,而且称作可移植的编译器仍旧依赖于源语言和目标语言。其部分原因是程序设计语言和机器构造的快速发展以及根本性的变化,但是有效地保持移植一个新的目标语言所需的信息 或使数据结构普遍地适合改变为一个新的源语言所需的信息却十分困难。然而人们不断分离前端和后端的努力会带来更方便的可移植性。

    折叠

    编译器发现,在生成代码之前多次处理整个源程序很方便。这些重复就是( pass)。首遍是从源中构造一个语法树或中间代码,在它之后的遍是由处理中间表示、向它增加信息、更换结构或生成不同的表示组成。遍可以和阶段相应,也可无关-遍中通常含有若干个阶段。实际上,根据语言的不同,编译器可以是一遍(one pass )-所有的阶段由一遍完成,其结果是编译得很好,但(通常)代码却不太有效。Pascal语言和C 语言均允许单遍编译。(Modula - 2语言的结构则要求编译器至少有两遍)。大多数带有优化的编译器都需要超过一遍:典型的安排是将一遍用于扫描和分析,将另一遍用于语义分析和源代码层优化,第3遍用于代 码生成和目标层的优化。更深层的优化则可能需要更多的遍:5遍、6遍、甚至8遍都是可能的。

    折叠语言定义和编译器

    程序设计语言的词法和语法结构通常用形式的术语指定,并使用正则表达式和上下文无关文法。但是,程序设计语言的语义通常仍然是由英语(或其他的自然语言)描述的。这些描述(与形式的词法及语法结构一起)一般是集中在一个语言参考手册(language reference manual )或语言定义(language definition)之中。因为编译器的编写者掌握的技术对于语言的定义有很大的影响,所以在使用了一种新的语言之后,语言的定义和编译器同时也能够得到开发。类似地,一种语言的定义对于构造编译器所需的技术也有很 大的关系。编译器的编写者更经常遇到的情况是:正在实现的语言是众所周知的并已有了语言定义。有时这个语言定义已达到了某个语言标准(language standard )的层次,语言标准是指得到诸如美国国家标准协会(American National Standards Institute ,ANSI )或国际标准化组织 (International Organization for Standardization,ISO )的官方标准组织批准的标准。FORTRAN、 Pascal和C语言就具有ANSI标准,Ada有一个通过了美国政府批准的标准。在这种情况下,编译器的编写者必须解释语言的定义并执行符合语言定义的编译器。通常做到这一点并不容易, 但是有时由于有了标准测试程序集(测试组(test suite )),就能够测试编译器(Ada有这样一个测试组),这又变得简单起来了。有时候,一种语言可从数学术语的形式定义(formal definition )中得到它的语义。现在人们已经使用了许多方法,尽管一个称作表示语义(denotational semantics )的方法已经成为较为常用的方法,在函数编程共同体中尤为如此,但现在仍然没有一种可成为标准的方法。当语言有一个形式定义时,那么在理论上就有可能给出编译器与该定义一致的数学证明,但是由于这太难了,而几乎从未有人做过。无论怎样, 运行时环境的结构和行为是尤其受到语言定义影响的编译器构造的一个方面。

    展开全文
  • 编译原理编译原理简单介绍

    万次阅读 多人点赞 2017-05-07 13:27:20
    编译原理简单介绍编译原理简单介绍 什么叫编译程序 翻译程序 编译程序 翻译和编译的区别 编译的过程 词法分析 语法分析 语义分析和中间代码的产生 优化 目标代码生成 编译程序的结构 编译程序总框 表格与表格的管理 ...

    编译原理简单介绍



    什么叫编译程序

    翻译程序

    翻译程序(Translator)是一种程序,其输入是某种语言的一系列语句,而其输出则是另一种语言的一系列语句,二者在逻辑上是等价的。就类似生活中的翻译官一样,把英语翻译成汉语,二者在意思上也是等价的。
    这里写图片描述

    编译程序

    编译程序(Compiler)是一种程序。它把用高级语言写的源程序作为数据接收,经过翻译转换,产生面向机器的代码作为输出。
    这当中代码还可能要由汇编程序或装配程序作进一步加工,得出目标程序,交给计算机执行。
    这里写图片描述

    翻译和编译的区别

    这里写图片描述

    编译的过程

    编译程序的工作过程一般可以分为5个阶段:
    1. 词法分析
    2. 语法分析
    3. 语义分析和中间代码的产生
    4. 优化
    5. 目标代码生成

    词法分析

    词法分析的任务是:输入源程序,对构成源程序的字符串进行扫描和分解,识别出一个个单词(定义符、标识符、运算符、界符、常数)。

    在词法分析阶段的工作中所依循的是语言的语法规则(或称构词规则)。
    描述语法规则的有效工具是正规式有限自动机

    语法分析

    语法分析的任务是:在词法分析的基础上,根据语言的语法规则,把单词符号串分解成各类语法单元(语法范畴)(短语、子句、句子、程序段、程序),并确定整个输入串是否构成语法上正确的程序。

    语法分析所依循的是语言的语法规则。
    语法规则通常用上下文无关文法描述。
    词法分析是一种线性分析,而语法分析是一种层次结构分析。

    语义分析和中间代码的产生

    这一阶段的任务是:对语法分析所识别出的各类语法范畴,分析其含义,并进行初步翻译(产生中间代码)。这一阶段通常包含两个方面的工作。
    1. 对每种语法范畴进行静态语义的检查,例如,变量是否定义、类型是否正确等等。
    2. 如果语义正确则进行中间代码的翻译。

    这一阶段所依循的是语言的语义规则,通常使用属性文法描述语义规则。

    优化

    对于代码(主要是中间代码)进行加工变换,以期能够产生更为高效(省时间和空间)的目标代码 。
    优化的主要方面有:公共子表达式的提取、循环优化、删除无用代码等等。

    优化所依循的是程序的等价变换规则。

    目标代码生成

    这一阶段的任务是:把中间代码(经过优化处理之后的)变换成特定机器上的低级语言代码(绝对指令、可重定位指令、汇编指令
    )。

    编译程序的结构

    编译程序总框

    这里写图片描述

    表格与表格的管理

    编译程序在工作过程中需要保持一系列的表格,以登记源程序的各类信息和编译各阶段的进展状况
    最重要的是符号表,用来等级源程序中出现的每个名字以及名字的各种属性。例如,一个名字是常量名还是变量名,还是过程名;如果是变量名,类型是什么,占多大内存,地址是多少等等。

    编译各阶段均须维持表格并进行表格管理,建表的技术支持是数据结构,表格的分类、结构、处理方法决定于语言及机器,还有优化措施。

    出错处理

    如果源程序有错误,编译程序应设法发现错误,并把有关错误的信息报告给用户。一个好的编译程序:
    1. 全
    最大限度发现错误
    2. 准
    准确指出错误的性质和发生地点
    3. 局部化
    将错误的影响限制在尽可能小的范围内
    4. 自动校正
    若能自动校正错误则更好,但其代价非常高

    源程序中的错误一般分为语法错误和语义错误。
    1. 语法错误
    指源程序中不符合不符合语法(或词法)规则的错误,例如:单词拼写错误、括号不匹配等等。
    2. 语义错误
    指源程序中不符合语义规则的错误,例如:说明错误、作用域错误、类型不匹配等等。
    一般在语义分析时检出来,有的语义错误要在运行时才能检测出来。

    遍 是对源程序或源程序的中间结果从头到尾扫描一次,并作有关的加工处理,生成新的中间结果或目标程序。遍数多了,整个编译程序的逻辑结构就比较清晰,但是会增加输入和输出所消耗的时间。因此,在主存可能的前提下,一般还是遍数少的好。
    分遍的依据:
    1. 源程序的结构
    2. 选用机型的内存大小
    3. 设计目标的技术指标
    4. 参加编译程序人员的数量、素质

    好的编译程序的指标:
    1. 符合语法规则的程序都可执行。
    2. 任何非法的错误都有可能识别,并尽量少的产生连锁反应。
    3. 错误不至于导致系统崩溃。
    4. 可维护和可读性。
    5. 模块化和结构化。

    编译的前端与后端

    概念上我们有时候把编译程序分成编译前端和编译后端。

    编译前端

    前端主要由源语言有关但与目标机无关的那些部分组成,通常包括词法分析、语法分析、语义分析与中间代码的产生,有的代码优化工作也可以包括在前端。

    编译后端

    后端包括编译程序中与目标机有关的那些部分,如与目标机有关的代码优化和目标代码生成等。
    通常后端不依赖源语言而仅仅依赖于中间语言。

    编译程序的生成

    编译程序的构造工具

    以前人们构造编译程序大多数采用的是机器语言或汇编语言,现在只有为了充分发挥各种不同硬件系统的效率,为了满足各种不同的具体要求,才会采用这种工具来构造编译程序(或编译程序的“核心”部分)。现在越来越多采用高级语言来构造编译程序。

    T型图

    为了便于说明,我们常采用T型图来表示源语言S、目标语言T、和比编译程序实现语言I之间的关系。
    每个T型图相当于一个编译程序。
    这里写图片描述

    用高级语言L1构造编译程序

    如果A机器上有一个使用A机器代码实现的某高级语言L1的编译程序(黄色),则我们可以使用L1语言编写另外一种高级语言L2的编译程序(橙色)。把写好的L2编译程序经过L1编译程序编译后就可以得到A机器码实习的L2编译程序(绿色)。
    这里写图片描述

    编译程序的移植

    通过上面用高级语言L1构造编译程序的原理,我们可以实现编译程序的“移植”。首先我们有一个可以在A机器上编译的高级语言L,
    这里写图片描述
    接下来我们使用L去写一个能够在B机器上运行的编译程序,
    这里写图片描述
    然后通过L的编译程序就可以生成在A机器上可以运行的产生B机器代码的编译程序(3)。
    这里写图片描述
    使用这个编译程序(3)去编译一遍(2)就可以得到能在B机器上运行的B机器代码的编译程序(4).
    这里写图片描述

    自编译方式

    先对语言的核心部分构造一个小小的编译程序(可用低级语言实现),再以它为工具构造能编译更多语言成分的较大编译程序,如此不断扩展,最后形成整个编译程序(滚雪球),这种通过一系列自展途径而形成的编译程序的过程叫做自编译过程。

    构造工具

    现在人们已经建立了多种编制部分编译程序或者整个编译程序的有效工具。构造编译程序的工具称为编译程序-编译程序、编译程序产生器或翻译程序书写系统。
    例如:
    自动产生扫描器:LEX FLEX
    自动产生语法分析器:YACC BISON

    展开全文
  • 编译原理试题汇总+编译原理期末试题(8套含答案+大题集)
  • 编译原理绪论

    千次阅读 2020-09-05 21:58:30
      之前一直在写程序,了解到运行程序的两个步骤:编译,运行。在Microsoft visual C++中编译和运行是分开的两部分。在DEV C++中集成为一个按键。在之前的印象中,编译就是寻找语法错误的过程...怎么才能实现编译原理

      之前一直在写程序,了解到运行程序的两个步骤:编译,运行。在Microsoft visual C++中编译和运行是分开的两部分。在DEV C++中集成为一个按键。在之前的印象中,编译就是寻找语法错误的过程。只要程序语法有错误,程序就无法通过编译。并会提示相应的信息,告诉写程序的人去哪里修改什么类型的错误。这学期开始,开设了编译原理课程。按照之前的习惯,通过写博客,及时梳理自己的思路并希望能在某些方面有所提高。下面开始吧!

      学东西先问自己几个问题:什么是编译原理?为什么需要编译原理?怎么才能实现编译原理?

      刚开始学习,对编译原理的理解不是那么深刻。简单谈一谈自己的看法。如果后续学习过程中发现自己理解有误,也会及时修改。

      为什么需要编译原理?
        了解计算机的人都知道对于计算机来说,它所能识别的只是机器代码。就是我们常说的0,1串。但对于编写程序的人来说,如果利用机器语言编写程序,那过程必将是痛苦且低效的。程序员为了让生活对自己好点,慢慢的开发出了高级语言,如C,C++,python等等。抽象来看,输入是高级语言,输出是机器语言,那中间的转化器就是编译器,其原理就是编译原理。现在知道为什么需要编译原理了吧!它是我们和计算机更好沟通的桥梁。

       什么是编译器?
        由之前的问题了解到,编译器的作用是把高级语言转换成低级语言(机器语言)。其实这简单的解释了编译器的定义。运用编译原理编写的程序,并能起到编译作用的程序叫做编译程序。把编译程序做一个推广:翻译程序。翻译程序的定义是:把某一种语言程序(称为源语言程序)转换成另一种语言程序(称为目标语言程序),而两种程序在逻辑上等价。对翻译程序的源语言(高级语言)和目标语言(机器语言)加以限制就成为了编译程序。

      怎么才能实现编译原理?
        问题有点抽象。转换的直接一点:编译程序怎么写?编写程序自然有它的步骤,下面会陆续介绍到编译过程的五大步骤。只有了解编译过程,才能对应去写如何实现这些编译过程的代码。

      回答完刚才的问题,想必对编译原理和编译程序有了少许的了解。下面开始进入正文:通过本篇文章,想解释明白下面几个问题:

      1.编译程序的五大步骤及各步骤的主要工作

      2.有关编译程序的几个名词解释,包括:表格管理、出错处理、遍、编译前端/后端

      3.编译程序的生成,自展和移植。

      注意:因为作者对C语言比较熟悉,所以编译程序的举例都以C语言作为基准。

    一、编译程序的五大步骤及各步骤的主要工作

      先对五大步骤做一个说明,再详细介绍各个步骤:

      1) 词法分析

      2) 语法分析

      3) 语义分析及中间代码生成

      4) 优化

      5) 目标代码生成

      它们的顺序其实已经说明其关系,但为了便于理解,这里附上一张关系图:
    在这里插入图片描述
      下面对各个步骤进行详细说明:

      1) 词法分析

      词法分析就是从头到尾扫描,识别出语法单元,并查看是否存在词法错误。那什么是词法错误呢?举个例子:在部分编程语言中,要求变量名只能由下划线或字母打头而不能使用数字。比如在C语言中的int 3a;就一个不合法的声明,这种错误会在词法分析是被检测出来。再举一个例子,作为编程新手常犯的错误:总是把英文的;写成中文的;这个在编译过程中都会报错,错误的阶段属于词法分析。

      在词法分析的过程中不止会寻找错误,也会对正确的词进行一个分类。包括以下几个类别:

         1. 基本字(保留字):主要指语言中已经定义好的那些字符,例如:int double typedef return等等

         2. 标识符:主要指在程序中定义的变量名。如int a;/double b;中a,b就属于标识符

         3. 界符:界符用于分隔程序,常见的界符有; { } 等

         4. 算符:主要指进行运算的字符,运算包括数值运算和逻辑运算。例如:+,-,*,/和&,|;

         5. 赋值符:完成的功能就是赋值,例如:=,+=,-=等

      举个例子,说明词法分析干的事情:

    在这里插入图片描述

      2) 语法分析

      语法分析主要作用是根据语法规则识别语法单位并分析语句中有没有语法错误(这句话听起来很抽象,啥算语法错误?)识别语法错误,就像a=+c其实本来应该是a=b+c,b没有写,对于词法分析过程只认定出是两个连续的算符。对于加号的语法是左右都要有变量,这就是语法错误。另一个作用是识别语法单位,。举个例子:A=B+C*60;是一个赋值语句,可以构建以下语法分析树,说明这是一个正确的赋值语句:

    在这里插入图片描述  使用语法分析树的好处有什么呢?
        好处在于能够表示出语句的层次结构,同时也可以用于辅助对语句的翻译。
        从下往上看,经历的算符分别是*到+再到赋值符=。这正好符合我们运算过程中对于优先级的体现(想想在运算中是不是先乘除,再加减,最后把结果赋值给左边)。

      对于词法分析和语法分析的总结:词法分析是一种线性分析,语法分析是一种层次结构分析。

      3) 语义分析与中间代码产生器

      语义分析在语法分析之后,所以能到语义分析说明程序中已不存在语法错误。还用之前的a=b+c举例。对于+语义来说,要求左右两个操作数的类型应当相同。对其的检测就属于语义分析的部分。还有一些与具体语言相关的,典型情况包括:变量在使用前先进行声明、每个表达式都有合适的类型、函数的调用和函数的定义相关。(参考博客:https://blog.csdn.net/hit_shaoqi/article/details/83120448link,该博客中列举很多语义分析的错误例子)

      中间代码生成的步骤要先理解什么是中间代码?中间代码就是用简单表达式表示你要进行的操作,举个例子就明白:
      对于A = B+C60;这个表达式生成的中间代码如下:
        temp1 = C
    60
        temp2 = B+temp1
        A = temp2

      对中间代码常用四元式表示,四元式的结构如下:

    在这里插入图片描述
      所以对于上面的中间代码转换为四元式如下:
    在这里插入图片描述

      4) 优化

      优化的意思很明显,就是对代码进行调整使其运行时效率更高(体现在时间/空间上)。优化主要有三个方面:公共子表达式的提取,循环优化(主要优化对象),删除无用代码

      下面对三种优化分别举例:

      公共子表达式的提取优化:
       优化前
         A = B + C;
         …
         D = B + C;
       优化后
         T = B + C;
         A = T;
         …
         D = T;

      循环优化:
       优化前
         for k=1 to 100
          M=I+10k;
          N=J+10
    k;
       优化后
         M=I;
         N=J;
         For k=1 to 100
          M=M+10;
          N=N+10;

      从上面可以看出,循环优化的主要思想是把乘除法优化为加减法。(我们都知道乘除运算所用时间远大于加减法)。

      删除无用代码:
        无用代码就是对程序的结果没有影响的代码。主要可分为两部分

       1. 复制传播
         如果存在语句y=x,并且下面有许多语句在使用y。由于x,y在数值上相等,所以可利用x代替所有的y。这样就可以减少y=x这条语句。这种思想就叫做复制传播。

       2. 无用代码(死code)
         在程序编写过程中,可能出现对一个变量赋值,但后面并没有使用该变量,就认为该赋值无效,将其删除。

      5) 目标代码生成:

      目标代码生成的过程非常复杂,就是把优化好的中间代码转换成指定的低级语言代码(汇编语言或者机器语言等等)。比如上面所举例子最终转换的汇编代码如下图:
    在这里插入图片描述
      从以上五个步骤可以看出一个明显的分界线,就是第三步中语义分析和中间代码生成之间。在中间代码生成之前,都没有对代码的任何翻译,所做的只是分析,故又称为分析部分。语义分析之后都是对代码的翻译调整,故又称为综合部分

    二、有关编译程序的几个名词解释,包括:表格管理、出错处理、遍、编译前端/后端

      表格管理:

        在编译程序中的表格主要的指的是符号表。符号表内存储的内容就是标识符(变量)的各种信息。例如:变量类型,存储位置等等。而对符号表的维护是贯彻在整个阶段的。(即每个阶段对符号表都会增添或删改)

      出错处理:

        在编写程序过程中难免出错,所以对于错误的处理就至关重要。出错可分为两大类:语法错误和语义错误。对于错误处理分几个层次,由坏到好分别为:检测到错误并暂停报错、检测到错误提示报错信息并继续执行程序以发现更多的错误、检测到错误并由对应的办法对错误进行处理校正。

      遍(趟):

        平时总说一遍,两遍。但这里的“遍”定义为对源程序或源程序的中间结果从头到尾扫描一次并作有关的加工处理,生成新的中间结果或目标程序。一遍即可以对一个阶段而言(比如把词法分析单独作为一遍),也可以包括多个阶段(比如把词法分析、语法分析、语义分析和中间代码产生和为一遍)。

      遍的作用是什么?
        “遍”可以使编译程序结构更清晰,每一遍可以集中处理关键问题

      编译前端/后端:

        在概念上一个编译程序可划分为编译前端和编译后端。

        编译前端主要由与源语言有关但与目标机无关的那部分组成的。这些部分通常包括词法分析,语法分析,语义分析与中间代码生成。

        编译后端主要由与目标机有关的那部分组成的。这些部分通常包括优化和目标代码生成。

        那为什么需要划分编译前端和后端呢?

        从上面的概念可以看出,中间代码处于编译前端和编译后端的过渡位置。做这样一个图:
    在这里插入图片描述
      从这个图看出,高级语言变成中间代码这部分叫前端。前端可以由不同的部分引起。同样也可以产生不同的后端。这样不同的前端可以对应相同的后端,相同的前端可以对应不同的后端。就可以很好的体现代码的移植性。对于移植性的介绍会在后面说明。

      在这里解释下为啥会有机器A,机器B的区分。在不同机器上的架构是不同的,据两个方面的例子。一方面是手机和pc机(一大一小,实现方式肯定不同),另一方面是指令集体系结构的不同,像CISC和RISC的实现结果肯定也不相同。

    三、编译程序的生成、自展和移植

      编译程序的生成有三大组成部分:源语言、编译程序、目标语言。编译程序的作用就是把源语言变成目标语言。更为具体:如果想让编译程序在目标机上运行,那么编译程序的编写语言需要是目标语言。(是不是很绕?这语言,那语言的)。为了更方便的说明,做了图形的规定(还是拿图说话!)取名为T形图:
    在这里插入图片描述 (s代表源语言,T代表目标语言,I代表编译程序)

      编译程序的自展

      给定一个目标:在机器A(目标机)上,用语言A(编译程序实现语言),构造高级语言L(源语言)的编译程序。T形图表示为:

    在这里插入图片描述
      Step1:可以考虑使用L的子集S,在机器A上用语言A构造S的编译程序。T形图如下:
    在这里插入图片描述
      Step2:再在机器A上,用语言S构造L的编译程序。T形图如下:

    在这里插入图片描述
      再将两者结合,step1中s可编译成A,step2中可以利用S对L进行编译并在A机器上执行。我们所希望的是L以A的机器语言在A机器上运行,由step1可将S转换成A机器代码,所以L语言就可以用A的机器代码在A的机器上运行(是不是很绕?你如果第一次学那肯定是一脸懵的)我们用图说话:
    在这里插入图片描述
      写完以上过程,我有个疑问:为什么需要s是L的子集?上网找到一种说法发现可以接受,也更为贴切于自展的名字。(下图中的L1就是L的子集,从子集出发才能够慢慢扩展为L)

    在这里插入图片描述
    (节选于博客:https://blog.csdn.net/NameOfBlog/article/details/82857644link

      编译程序的移植:

      什么叫编译程序的移植呢?就是在A机器上已有的高级语言L编写一个在B机器上运行的高级语言L的编译程序。(简单来说就是L目前已经能使用A机器语言在机器A上运行,想要L能使用B机器语言在机器B上运行)T形图如下:
    在这里插入图片描述
      过程简记为一次编程两次编译

       一次编程:使用L语言编写能够让L语言产生B机器代码的编译程序R

       第一次编译:把R在A机器代码下编译使其变为A机器代码的语言,记为编译程序I,作用是把L编译成B

       第二次编译:用编译程序I对R进行编译,成功完成L使用B机器语言完成的汇编程序在机器B上运行。

       就单单这三段话就能让人找不着北!!!我们还是看图说话:
    在这里插入图片描述
      文字配图,事半功倍:

      第一次编程,我们用L语言实现了编译程序R。注意R的本质是L语言,只不过功能是把L翻译成B。既然是L语言,通过已知L->A,我们可以把L语言变成A语言。这里的变成只是实现方式的改变,并没有改变原有的功能,还是一次编程的图,作用还是变成B语言,区别在于原来是用L语言实现的,现在用了A语言。也可以这样理解:

      原来是L1-(L2)->B,又因为L2-(A)->A1。于是L1-(A1)->B。由一次编程知L通过R可变成B,现在只要R变成B就可以了。R的本质是L,第一次编译实现了L变成B,利用第一次的编译程序就可以把L变成B。这样R就变成了B就实现L-(B)->B。

      刚学了绪论,对于编译原理的各个部分了解还过于粗浅。用了将近5000字才完成这篇文章。随着后面的学习,会更深层次的剖析每一个部分的。继续加油哈!

      致谢:感谢编译原理课程谢老师对文章的耐心修改,同样感谢网上各位博主的优秀博文,为我不懂的地方提供解决办法。

    因作者水平有限,如有错误之处,请在下方评论区指正,谢谢!

    展开全文
  • 编译原理实验:词法分析

    万次阅读 多人点赞 2018-09-29 21:17:16
    编译原理实验:词法分析1. 实验题目:词法分析实验目的实验内容实验要求输入输出2. 设计思想3.算法流程4. 源程序5. 调试数据 1. 实验题目:词法分析 实验目的 根据PL/0语言的文法规范,编写PL/0语言的词法分析...

    1. 实验题目:词法分析

    实验目的

    1. 根据PL/0语言的文法规范,编写PL/0语言的词法分析程序;或者调研词法分析程序的自动生成工具LEX或FLEX,设计并实现一个能够输出单词序列的词法分析器。
    2. 通过设计调试词法分析程序,实现从源程序中分出各种单词的方法;加深对课堂教学的理解;提高词法分析方法的实践能力。
    3. 掌握从源程序文件中读取有效字符的方法和产生源程序的内部表示文件的法。
    4. 掌握词法分析的实现方法。上机调试编出的词法分析程序。

    实验内容

     已给PL/0语言文法,输出单词符号(关键字、专用符号以及其它标记)。

    实验要求

    1. 把词法分析器设计成一个独立一遍的过程。
    2. 词法分析器的输出形式采用二元式序列,即:(单词种类,单词的值)

    输入输出

    输入:
      const a=10;
      var b,c;
      begin
      read(b);
      c:=a+b;
      write( c);
      end.
    输出:
      (constsym, const)
      (ident , a)
      (eql, =)
      (number, 10)
      (semicolon, ; )
      (varsym, var)
      (ident,b)
      (comma, ,)
      (ident, c)
      (semicolon, ; )
      (beginsym, begin)
      (readsym, read )
      (lparen,( )
      (ident, b)
      (rparen, ))
      (semicolon, ; )
      (ident, c)
      (becomes, := )
      (ident, a)
      (plus, +)
      (ident,b )
      (semicolon, ; )
      (writesym,write)
      (lparen, ( )
      (ident, c)
      (rparen,) )
      (endsym, end )
      (period, .)

    2. 设计思想

    基本字:

    单词(编码) 正规式r
    begin(beginsym) begin
    call(callsym) call
    const(constsym) const
    do(dosys) do
    end(endsym) end
    if(ifsym) if
    odd(oddsym) odd
    procedure(proceduresym) procedure
    read(readsym) read
    var(varsym) var
    while(whilesym) while
    write(writesym) write
    then(thensym) then

    标识符:

    单词(编码) 正规式r
    <标识符>(ident) (字母)(字母 |数字)*

    常数:

    单词(编码) 正规式r
    <常数>(ident) (数字)(数字)*

    运算符:

    单词(编码) 正规式r
    +(plus) +
    -(minus) -
    *(times) *
    /(slash) /
    =(eql) =
    <>(neq) <>
    <(lss) <
    <=(leq) <=
    >(gtr) >
    >=(geq) >=
    :=(becomes) :=

    界符:

    单词(编码) 正规式r
    ( (lparen) (
    ) (rparen) )
    , (comma) ,
    ; (semicolon) ;
    . (period) .

    3.算法流程

    在这里插入图片描述

    1. 词法分析程序打开源文件,读取文件内容,直至遇上文件结束符,然后读取结束。
    2. 接下来就要对源文件从头到尾进行扫描了,从头开始扫描,这个时候扫描程序首先要询问当前的字符是不是空格,若是空格,则继续扫描下一个字符,直至不是空格。然后询问这个字符是不是字母,若是则进行标识符和保留字的识别;若这个字符为数字,则进行数字的判断。否则,依次对这个字符可能的情况进行判断(界符和运算符),若将所有可能都走了一遍还是没有知道它是谁,则认定为错误符号,输出该无法识别error,程序结束。每次成功识别了一个单词后,单词都会存在word1[]数组中,然后字符指针往后移,进行下一个单词的识别。
    3. 主控程序需要负责对每次识别的种别码进行判断,对于不同的单词种别做出不同的反应,直至文件结束。
    4. 本次实验我采用了map这个STL关联容器,主要是考虑到词法分析中的数据映射的关系,因此采用这种结构。map提供一对一的数据处理能力,其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值。这个容器是非常方便使用的,对于查找可以直接使用迭代器进行,利用find()函数,若一直到末尾都未找到,则是不能识别或为标识符。

    4. 源程序

    #include<bits/stdc++.h>
    using namespace std;
    map<string,string> word;//应用map数据结构形成一个string->string的对应
    std::map<string,string>::iterator it;//用来遍历整个对应关系的迭代器
    void map_init(){//对应关系进行初始化
        word["begin"]="beginsym";
        word["call"]="callsym";
        word["const"]="constsym";
        word["do"]="dosym";
        word["end"]="endsym";
        word["if"]="ifsym";
        word["odd"]="oddsym";
        word["procedure"]="proceduresym";
        word["read"]="readsym";
        word["then"]="thensym";
        word["var"]="varsym";
        word["while"]="whilesym";
        word["write"]="writesym";
        word["+"]="plus";
        word["-"]="minus";
        word["*"]="times";
        word["/"]="slash";
        word["="]="eql";
        word["<>"]="neq";
        word["<"]="lss";
        word["<="]="leq";
        word[">"]="gtr";
        word[">="]="geq";
        word[":="]="becomes";
        word["("]="lparen";
        word[")"]="rparen";
        word[","]="comma";
        word[";"]="semicolon";
        word["."]="period";
    }
    int main(){
        map_init();//初始化
        char ch;
        char a;
        string word1;//string变量识别单词
        string str;//string变量进行字符识别
        ifstream infile("F:\\编译原理\\第一次实验\\analysis.txt");//文件输入流
        ofstream outfile("F:\\编译原理\\第一次实验\\result.txt");//文件输出流
        ostringstream buf;
        while(buf&&infile.get(ch)) buf.put(ch);//将文件中的字符读出来
        str= buf.str();//将得到的字符储存到string类型变量中
        int csize=str.length();
        for(int i=0;i<csize;i++){//对整个字符串进行遍历
            while(str[i]==' '||str[i]=='\n') i++;//若最开始为空格或换行符,则将指针的位置往后移
            if(isalpha(str[i])){//对标识符和基本字进行识别,调用库函数isalpha()
                word1=str[i++];
                while(isalpha(str[i])||isdigit(str[i])){
                    word1+=str[i++];
                }
                it=word.find(word1);
                if(it!=word.end()){//判断是不是基本字,若为基本字则进行输出
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }
                else{//否则直接输出
                    cout<<"(ident"<<","<<word1<<")"<<endl;
                }
                i--;
            }
            else if(isdigit(str[i])){//判断是不是常数,调用库函数isdigit()
                word1=str[i++];
                while(isdigit(str[i])){
                    word1+=str[i++];
                }
                if(isalpha(str[i])){
                    cout<<"error!"<<endl;
                    break;
                }
                else{
                    cout<<"(number"<<","<<word1<<")"<<endl;
                }
                i--;
            }else if(str[i]=='<'){//对<,<=分别进行判断
                word1=str[i++];
                if(str[i]=='>'){
                    word1+=str[i];
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }else if(str[i]=='='){
                    word1+=str[i];
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }else if(str[i]!=' '||!isdigit(str[i])||!isalpha(str[i])){
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }else{
                    cout<<"error!"<<endl;
                    break;
                }
                i--;
            }else if(str[i]=='>'){//对>,>=分别进行判断
                word1=str[i++];
                if(str[i]=='='){
                    word1+=str[i];
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }else if(str[i]!=' '||!isdigit(str[i])||!isalpha(str[i])){
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }else{
                    cout<<"error!"<<endl;
                    break;
                }
                i--;
            }else if(str[i]==':'){//对:=进行判断
                word1=str[i++];
                if(str[i]=='='){
                    word1+=str[i];
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }else{
                    cout<<"error!"<<endl;
                    break;
                }
                i--;
            }else{//对其他的基本字依次进行判断
                word1=str[i];
                it=word.find(word1);
                if(it!=word.end()){
                    cout<<"("<<word[word1]<<","<<word1<<")"<<endl;
                }else{
                    cout<<"error!"<<endl;
                    break;
                }
            }
        }
        infile.close();
        return 0;
    }
    

    5. 调试数据

    待输入的文件流:
    在这里插入图片描述
    输出数据:
    在这里插入图片描述
    在这里插入图片描述
    说明:如上实验仅符合当时实验要求的相关条件,其他的需求略微进行更改就行,思想是一样的,还是很简单的。输入输出自己按自己需求更改即可。

    展开全文
  • 编译原理作业3 所有题目均为自己所写,觉得有用可以点个赞哟:-) 答案仅供参考,若有问题欢迎评论区讨论~ 文章目录编译原理作业34.2.14.2.24.2.34.2.54.4.14.4.44.6.54.6.64.7.24.7.5 4.2.1 考虑上下文无关...
  • 编译原理及交叉编译

    2018-04-20 12:51:17
    转载: https://blog.csdn.net/testmyieda22/article/details/51444804编译原理及交叉编译编译原理gcc/g++在执行编译的时候,只要分四个阶段 :1、预处理阶段,完成宏定义和include文件展开等工作;不生成文件 [预...
  • 编译原理总结

    2018-06-11 08:53:39
    编译原理是计算机专业的一门重要课程,主要介绍在编译程序构造的一般原理和方法,其中有, 编译原理是计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法...
  • 简介DOTNET 编译原理 相信大家都使用过 Dotnet ,可能还有不少高手。不过我还要讲讲Dotnet的基础知识,Dotnet的编译原理。 Dotnet是一种建立在虚拟机上执行的语言,它直接生成 MSIL 的中间语言,再由DotNet编译器 ...
  • 编译原理概述

    2016-11-09 20:21:31
    编译原理的应用 准备系统地整理学习编译原理的相关知识。虽然这部分知识很多时候非常抽象,却能够很高的提高程序员内在的修养。如果每一种程序语言是一套招式,那么编译原理就是内功。不管程序语言如何变,都摆脱...
  • 编译原理】引论

    千次阅读 2020-02-18 20:59:54
    文章目录编译原理引论(一)认识编译程序(二)编译过程概述1、阶段划分2、编译程序的结构3、编译程序的生成 编译原理引论 (一)认识编译程序 什么是编译程序? 这要从翻译程序、解释程序以及编译程序的联系与区别...
  • 编译原理:总结

    万次阅读 多人点赞 2019-01-08 09:34:59
    类型错误(声明void f()和调用aa = f()) 逻辑:无限循环/递归调用( == → = ) 错误处理目标 清楚、准确地检测、报告错误及其发生位置 快速恢复,继续编译,以便发现后续错误 不能对正确程序的编译速度造成很大影响 ...
  • Cmake编译原理

    千次阅读 2019-06-02 17:17:26
    CMake编译原理   CMake是一种跨平台编译工具,比make更为高级,使用起来要方便得多。CMake主要是编写CMakeLists.txt文件,然后用cmake命令将CMakeLists.txt文件转化为make所需要的makefile文件,最后用make命令...
  • 编译原理课程作为计算机专业本科生的必修课,能够使学生们增加对编译过程的了解。因为想加深对编译器的了解和实现一个简单的编译器,所以我找到了该课程,通过阅读该课程的简介,觉得该课程还是十分不错的,还能够让...
  • 了解编译原理

    2019-10-09 02:36:27
    了解编译原理 1)简述编译程序与翻译程序、汇编程序的联系与区别。 编译程序就是将源代码文件以字符流的形式进行处理,进行词法和语法的分析,然后通过汇编器将源代码指令转变成汇编指令(由编译器将c...
  • 编译原理:了解编译原理

    千次阅读 2019-09-25 11:59:06
    1)简述编译程序与翻译程序、汇编程序的联系与区别。 个人理解:编译程序(为高级服务)是将高级语言书写的源程序翻译成与之等价的低级语言的目标程序。  翻译程序是指把高级语言源程序翻译成机器语言源程序...
  • 编译原理序列

    千次阅读 2013-09-11 00:01:50
    表达式翻译器-1-编译原理 词法分析器-2-编译原理 递归下降法的语法分析器-3-编译原理 递归下降法的语法分析器-3.1-编译原理 用Yacc实现语法分析器-4-编译原理 中间代码生成器-5-编译原理

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 38,537
精华内容 15,414
关键字:

编译原理