程序设计语言—编译原理

 

程序设计语言—编译原理

(2010-10-28 01:04:39)
标签:

校园

分类: 工作篇
一、编译原理的历史发展
第一个编译程序的出现是在20世纪50年代早期,很难讲出确切的时间,因为当初大量的实验和实现工作是由不同的小组独立完成的,多数早期的编译工作是将算术公式翻译成机器代码。用现在的标准来衡量,当时的编译程序能完成的工作十分初步,如只允许简单的单目运算,数据元素的命名方式有很多限制。然而它们奠定了对高级语言编译系统的研究和开发的基础。20世纪50年代中期出现了FORTRAN等一批高级语言,相应的一批编译系统开发成功。随着编译技术的发展和社会对编译程序需求的不断增长,20世纪50年代末有人开始研究编译程序的自动生成工具,提出并研制编译程序的编译程序。它的功能是以任一语言的词法规则、语法规则和语义解释出发,自动产生该语言的编译程序。目前很多自动生成工具已广泛使用,如词法分析程序的生成系统LEX,语法分析程序的生成系统YACC等。20世纪60年代起,不断有人使用自展技术来构造编译程序。自展的主要特征是用被编译的语言来书写该语言自身的编译程序。1971年,PASCAL的编译程序用自展技术生成后,其影响就越来越大。
  随着并行技术和并行语言的发展,处理并行语言的并行编译技术,将串行程序转换成并行程序的自动并行编译技术也正在深入研究之中。另外嵌入式应用迅速增长的需求,推动了交叉编译技术的发展.还有系统芯片设计方法和关键EDA技术的研究,也带动了专用语言VHDL等及其编译技术的不断深化。
 
二、编译原理工具的具体发展
随着大规模集成电路、网络通讯和其它数字信息技术的迅速发展,目前嵌入式系统己经广泛地渗透到科学研究、工程设计、军事技术、各类产业和商业文化艺术等各方面,在人们日常生活中的方方面面到处是嵌入式系统设备的身影,如手机、PDA、智能家电以及汽车电子等。随着国内外各种嵌入式产品的进一步开发和推广,嵌入式技术和人们的生活结合越来越紧密,人们日常的工作、学习和生活方式终将不可避免地逐步改变,嵌入式产品正在逐步形成时尚,在当今的信息社会中扮演越来越重要的角色。
嵌入式系统一般指的是非PC系统,它包括完全植入嵌入式硬件内部的为特定应用设计的专用计算机系统,以及相应的硬件。嵌入式系统以应用为中心的,它的软硬件可以根据需求进行裁减,以此来适应目标系统对外形尺寸、功能、可靠性、成本、功耗、外部接口等方面的严格要求。简单地说,嵌入式系统集操作系统、应用软件与硬件于一体,具有软件代码小、高度自动化、响应速度快等特点,因此特别适合要求实时和多任务的应用。
    由于嵌入式系统资源有限,一般无法提供编译、汇编、链接等工具,同时也很难提供高级调试功能,因此嵌入式系统的应用软件一般不能直接在嵌入式系统平台上进行开发,而需要在交叉编译环境中开发。也就是说通常是在另外的开发平台上开发的,一般这种开发平台由通用的计算机系统和专用的嵌入式系统软件开发工具组成。
编译实现方式的发展主要分一下五类:手工、机器语言、汇编、系统程序设计语言、自动构造工具lex yacc gcc。推动编译技术发展的因素主要包括:语言范型(计算模式)、计算机体系结构语言范型主要包括:命令式(imperative language) 、应用式(applicative) 、基于规则的(rule-based)、面向对象的(object-oriented)、并行计算(parallel computing)。
体系结构主要包括:万诺曼机体系结构、并行体系结构、嵌入系统。编译程序执行环境主要包括:批处理、交互环境、嵌入系统环境、并行编译技术、交叉编译。  编译程序在一个机器(宿主机)上运行,产生另一个机器(目标机)的汇编语言。嵌入式系统中的应用程序正是借助这样的编译程序生成。
目标处理器MIPSX是MIPS系列芯片的种,属于RISC体系结构,来源于斯坦福大学的MIPS计划。由于该系列CPU不是采用加州大学伯克利分校的RISC窗口技术而是采用消除流水线各级互锁的微处理器MIPS(MicroprocessorWithout Interlocking Pipeline Stage)技术,因此而得名。MIPS是将IBM公司对优化编译程序的研究和加州大学伯克利分校的大规模集成电路的思想结合起来的产品。
  由于RISC指令集的简单和整齐,为了达到更好地利用计算机的性能,MIPS系列芯片中很好地应用了流水线策略。流水线是现代各类微处理器都采用的指令执行技巧,即将若干条指令的取指、译码和执行过程部分重叠在流水线中同时执行。以前在CISC计算机中,由于指令多而复杂,处理每条指令的所需时间不固定,当后面指令需要前条指令的结果时,往往造成指令互锁,因此无法实现流水线。而斯坦福大学的MIPS计划就是在编译的过程中,利用编译程序优化处理器的流水线以求提高处理器流水线的效率。由于采用了硬件连线控制来执行数目不多的简单指令,而且还能重组软件流水线,这样就减少了硬件复杂性。
来源:(http://blog.sina.com.cn/s/blog_5a5dec0a0100c8uz.html?retcode=0) - 浅谈编译原理近期发展_ruan_新浪博客
但是由于存在数据和指令转移的相关性,这会引起流水线的停顿,降低流水线整体的执行速率。为了调整这些相关性,又开发出了代码重组技术,其中一种是延迟转移(delayed branch),另一种叫延迟装入,提升了性能。
 MIPS公司的R系列就是在此基础上开发的RISC工业产品的微处理器。这些系列产品被很多计算机公司采用生产各种工作站和计算机系统。R系列遵循按比例提高性能设计技术,按不同工艺技术实现基本相同的体系结构,其适用范围从低端的嵌入式控制器、个人计算机到高端的超级小型机、服务器甚至大型机和巨型机,而且系统软件和应用程序都是兼容的。MIPS公司在1986年推出82000处理器,1988年推出83000处理器,1991年推出第一款64位商用微处理器84000。之后,又陆续推出88000(于1994年)、810000(于1996年)和812000(于1997年)等型号。1999年,MIPS公司发布MIPS 32和MIPS 64架构标准。2000年,MIPS公司发布了针对MIPS 32 4Kc的新版本以及未来64位MIPS 64 20Kc处理器内核。
 在整个R系列中82000/82010是最基础的原型;83000/83010是82000/82010的增强型产品;由于84000采用高精度的CMOS工艺,因此其性能很高,用途很广;而86000/86010是ECL电路化的高速品种,但是由于86000/86010的功耗大,成本高,所以其应用受到很大限制。但是MIPSX并不属于以上提到的CPU中的任何一种,它是由20世纪80年代后期由美国国防部高级研究项目署(DARPA)资助的一个项目的成果。因此,基于MIPSX的交叉编译工具链研究虽然现有的GNU交叉编译工具链对MIPS公司R系列芯片的支持很好,但还是缺乏对MIPSX的有效支持,所以还是需要进行移植。进行移植工作前,必须首先了解MIPSX的体系结构。经过实验室前几届师兄的分析,我们得知MIPSX的体系结构与MIPS公司R系列芯片中的82000最为接近,当然它们在很多地方还是存在着差别,比如具体指令集的不同,比如MIPSX没有浮点操作;MIPSX指令的基本操作码只占5位;MIPSX在跳转指令中的延时槽有两条等。
 
三、编译原理课程教材和实践性的发展
“编译原理”课程分析编译原理课程一般利用常用软件如C,Pascal等来构建编译的各部分程序,编译原理的构成包括词法分析,语法分析、语义分析、中间代码生成、代码优化和目标代码生成等。编译原理课程的学习可以对程序设计语言的设计和实现有深刻的理解,还可以有助于快速理解定位和解决在程序编译、测试与运行中出现的问题。编译程序规模大。由于编译原理是一个极其复杂的系统,程序规模大,将它肢解开来一部分一部分地研究。理论知识抽象。要完整地构造一个编译系统并不是一件容易的事情,它不仅需要具有较完备的软件知识,并需要掌握现有的软件工具的使用,而且更重要的是要有丰富的实践经验,了解硬件系统结构和操作系统的功能。算法的理解和实现。编译原理这门课包含许多理论知识和算法,这些理论的学习和理解都存在着一定的难度。其中理论知识包括:词法分析器的构造,语法中各种分析器(LR,LL,SLR,LALR等)实现与完成。
在编译原理的不断发展和完善过程中,一本本课程教材接踵而至,其中不乏非常优秀的“名书”。编译领域里程碑式的经典著作——龙书,20年后终于出新版!这是一个延绵30年的故事,这是一部关于龙书的传奇!最新版本,增添三章节内容,使龙书地位更权威! 1977年,Alfred V. Aho 和Jeffrey D. Ullman 出版《Principles of Compiler Design 》,封面是一名骑士和一只恐龙,因此第一次被人称为龙书,但因为那条龙是绿色的,所以称为绿龙书。过了9年,1986年,原来的两位作者加上Ravi Sethi, 升级了前一本书,书名改为《Compilers: Principles, Techniques and Tools》,封面依然沿用骑士和恐龙,那头龙是红色的,因此被叫做龙书二或者是红龙书。又过了一个9年,又一个9年,编译领域的巨无霸--龙书始终都没有升级。终于在2006年年底,龙书升级了。作者又增加了Monica S. Lam,名字沿用《Compilers: Principles, Techniques and Tools》,封面依然保持恐龙和武士的设计,但这次的龙是紫色,因此叫做紫龙书。
本书全面、深入地探讨了编译器设计方面的重要主题,包括词法分析、语法分析、语法制导定义和语法制导翻译、运行时刻环境、目标代码生成、代码优化技术、并行性检测以及过程间分析技术,并在相关章节中给出大量的实例。与上一版相比,本书进行了全面的修订,涵盖了编译器开发方面的最新进展。每章中都提供了大量的系统及参考文献。本书是编译原理课程方面的经典教材,内容丰富,适合作为高等院校计算机及相关专业本科生及研究生的编译原理课程的教材,也是广大技术人员的极佳参考读物。Alfred V. Aho是哥伦比亚大学的Lawrence Gussman计算机科学教授。 Aho教授多次获奖,其中包括哥伦比亚校友会颁发的2003年度Great Teacher奖和电子与电器工程师协会的Jonh von Neumann奖章。他是美国国家工程院院士,以及ACM和IEEE的会员。Monica S. Lam是斯坦福大学的计算机科学教授。她曾经是Tensilica 的首席科学家,并且是moka5的创建者和首席执行官。她领导了 SUIF项目。该项目开发了最流行的研究性编译器之一,并首创了很多在工业界得到应用的编译技术。Jeffery D.Ullman是Gradiance公司的首席执行官和Standford大学的StanfordcW.Ascherman计算机科学(名誉退休)教授他的研究兴趣包括数据库理论、数据库集成、数据挖掘和利用信息基础软件的教育技术.他是美国国家工程院的院士,ACM的会员,并且是 Karlstrom奖和Knuth奖的获得者。
 
四、总结
在编译原理领域的技术人员和专家的共同努力下,编译原理近几年在各方面都有了迅猛的发展。特别是能在最前沿有所进步,这是顶尖科学工作者不懈努力的结果。在编译工具方面,也有了一定的发展,由于编译原理产生时间并不是很久,能有现在的发展应该说是很不错了。对于编译课程,也在不断进行改革,通过教育来培养更多编译领域的人才。
 
 
 
 
 
编译原理是计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。编译原理是计算机专业设置的一门重要的专业课程。虽然只有少数人从事编译方面的工作,但是这门课在理论、技术、方法上都对学生提供了系统而有效的训练,有利于提高软件人员的素质和能力。目前各个大学使用的教材机械工业出版社、国防工业出版社出版的《编译原理》。

基本概念

   编译器 是将一种语言翻译为另一种语言的计算机 程序 。编译器将 源程序 (source language) 编写的程序作为输入,而产生用目标语言(target language )编写的等价程序。通常地,源程序为高级语言(high-level language ),如 C 或C + + ,而目标语言则是目标机器的目标代码 (object code,有时也称作机器代码(machine code )),也就是写在计算机机器 指令 中的用于运行的代码。这一过程可以表示为:   源程序→编译器 →目标程序

发展历程

  在20世纪40年代,由于冯·诺伊曼在存储-程序计算机方面的先锋作用,编写一串代码或程序已成必要,这样计算机就可以执行所需的计算。开始时,这些程序都是用机器语言(machine language )编写的。机器语言就是表示机器实际操作的数字代码,例如:   C7 06 0000 0002 表示在IBM PC 上使用的Intel 8x86处理器将数字2移至地址0 0 0 0 ( 16进制 )的指令。   但编写这样的代码是十分费时和乏味的,这种代码形式很快就被 汇编语言 (assembly language )代替了。在汇编语言中,都是以符号形式给出指令和存储地址的。例如,汇编语言指令 MOVX, 2 就与前面的机器指令等价(假设符号存储地址X是0 0 0 0 )。汇编程序(assembler )将汇编语言的符号代码和存储地址翻译成与机器语言相对应的数字代码。   汇编语言大大提高了编程的速度和准确度,人们至今仍在使用着它,在编码需要极快的 速度 和极高的简洁程度时尤为如此。但是,汇编语言也有许多缺点:编写起来也不容易,阅读和理解很难;而且汇编语言的编写严格依赖于特定的机器,所以为一台计算机编写的代码在应用于另一台计算机时必须完全重写。   发展 编程技术 的下一个重要步骤就是以一个更类似于数学定义或自然语言的简洁形式来编写程序的操作,它应与任何机器都无关,而且也可由一个程序翻译为可执行的代码。例如,前面的汇编语言代码可以写成一个简洁的与机器无关的形式 x = 2 。   在1954年至1957年期间, IBM John Backus 带领的一个研究小组对 FORTRAN语言 及其编译器的开发,使得上面的担忧不必要了。但是,由于当时处理中所涉及到的大多数程序设计语言的翻译并不为人所掌握,所以这个项目的成功也伴随着巨大的辛劳。几乎与此同时,人们也在开发着第一个编译器, Noam Chomsky 开始了他的自然语言结构的研究。他的发现最终使得编译器结构异常简单,甚至还带有了一些自动化。 Chosmky的研究导致了根据语言文法(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的标准并没有多少,但是已沿着这一方向对标准的窗口环境进行开发了。

相关程序

  (1)解释程序(interpreter)    解释程序 是如同编译器的一种语言翻译程序。它与编译器的不同之处在于:它立即执行源程序而不是生成在翻译完成之后才执行的目标代码。从原理上讲,任何程序设计语言都可被解释或被编译,但是根据所使用的语言和翻译情况,很可能会选用解释程序而不用编译器。例如,我们经常解释 BASIC语言 而不是去编译它。类似地,诸如LISP 的函数语言也常常是被解释的。   解释程序也经常用于教育和软件的开发,此处的程序很有可能被翻译若干次。而另一方面,当执行的速度是最为重要的因素时就使用编译器,这是因为被编译的目标代码比被解释的源代码要快得多,有时要快10倍或更多。但是,解释程序具有许多与编译器共享的操作,而两者之间也有一些混合之处。   (2)汇编程序(assembler)    汇编程序 是用于特定计算机上的汇编语言的翻译程序。正如前面所提到的,汇编语言是计算机的机器语言的符号形式,它极易翻译。有时,编译器会生成汇编语言以作为其目标语言,然后再由一个汇编程序将它翻译成目标代码。   (3)连接程序(linker)   编译器和汇编程序都经常依赖于 连接程序 ,它将分别在不同的 目标文件 中编译或汇编的代码收集到一个可直接执行的文件中。在这种情况下,目标代码,即还未被连接的机器代码,与可执行的机器代码之间就有了区别。连接程序还连接目标程序和用于标准库函数的代码,以及连接目标程序和由计算机的操作系统提供的资源(例如,存储分配程序及输入与输出设备)。连接程序现在正在完成编译器最早的一个主要活动(这也是“编译”一词的用法,即通过收集不同的来源来构造)。连接过程对操作系统和处理器有极大的依赖性,。   (4)装入程序(loader)   编译器、汇编程序或连接程序生成的代码经常还不完全适用或不能执行,但是它们的主要存储器访问却可以在存储器的任何位置中且与一个不确定的起始位置相关。这样的代码被称为是可重定位的(relocatable ),而 装入程序 可处理所有的与指定的基地址或起始地址有关的可 重定位 的地址。装入程序使得可执行代码更加灵活,但是装入处理通常是在 后台 (作为操作环境的一部分)或与连接相联合时才发生,装入程序极少会是实际的独立程序。   (5)预处理器(preprocessor )    预处理器 是在真正的翻译开始之前由编译器调用的独立程序。预处理器可以删除注释、包含其他文件以及执行宏( macro是一段重复文字的简短描写)替代。预处理器可由语言(如 C )要求或以后作为提供额外功能(诸如为FORTRAN提供Ratfor预处理器)的附加软件。   (6)编辑器(editor)   编译器通常接受由任何生成标准文件(例如 ASCII文件 )的编辑器编写的源程序。现在,编译器已与另一个编辑器和其他程序捆绑进一个交互的开发环境-IDE中。此时,尽管编辑器仍然生成标准文件,但会转向正被讨论的程序设计语言的格式或结构。这样的编辑器称为基于结构的(structure based ),且它早已包括了编译器的某些操作;因此,程序员就会在程序的编写时而不是在编译时就得知错误了。从编辑器中也可调用编译器以及与它共用的程序,这样程序员无需离开编辑器就可执行程序。   (7)调试程序(debugger )    调试程序 是可在被编译了的程序中判定执行错误的程序,它也经常与编译器一起放在IDE 中。运行一个带有调试程序的程序与直接执行不同,这是因为调试程序保存着所有的或大多数源代码信息(诸如行数、变量名和过程)。它还可以在预先指定的位置(称为 断点 (breakpoint ))暂停执行,并提供有关已调用的函数以及变量的当前值的信息。为了执行这些函数,编译器必须为调试程序提供恰当的符号信息,而这有时却相当困难,尤其是在一个要优化目标代码的编译器中。因此,调试又变成了一个编译问题。   (8)描述器(profiler)   描述器是在执行中搜集目标程序行为统计的程序。程序员特别感兴趣的统计是每一个过程的调用次数和每一个过程执行时间所占的百分比。这样的统计对于帮助程序员提高程序的执行速度极为有用。有时编译器也甚至无需程序员的干涉就可利用描述器的输出来自动改进目标代码。   (9)项目管理程序(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是程序大小的度量(通常是字符数)。本节将讲述一些主要的数据结构,它们是其操作部分阶段所需要的,并用来在阶段中交流信息。   (1)记号(token)   当扫描程序将字符收集到一个 记号 中时,它通常是以符号表示这个记号;这也就是说,作为一个枚举数据类型的值来表示源程序的记号集。有时还必须保留字符串本身或由此派生出的其他信息(例如:与标识符记号相关的名字或数字记号值)。在大多数语言中,扫描程序一次只需要生成一个记号(这称为单符号先行(single symbol lookahead))。在这种情况下,可以用全程变量放置记号信息;而在别的情况(最为明显的是FORTRAN)下,则可能会需要一个记号数组。   (2)语法树(syntax tree)   如果分析程序确实生成了语法树,它的构造通常为基于 指针 的标准结构,在进行分析时动态分配该结构,则整棵树可作为一个指向根节点的单个变量保存。结构中的每一个节点都是一个 记录 ,它的域表示由分析程序和之后的语义分析程序收集的信息。例如,一个表达式的数据类型可作为表达式的语法树节点中的域。有时为了节省空间,这些域也是动态分配或存放在诸如符号表的其他数据结构中,这样就可以有选择地进行分配和释放。实际上,根据它所表示的语言结构的类型(例如:表达式节点对于语句节点或声明节点都有不同的要求),每一个语法树节点本身都可能要求存储不同的属性。在这种情况下,可由不同的记录表示语法树中的每个节点,每个节点类型只包含与本身相关的信息。   (3)符号表(symbol table)   这个数据结构中的信息与标识符有关: 函数 、变量、 常量 以及数据类型。符号表几乎与编译器的所有阶段 交互 :扫描程序、分析程序或将标识符输入到表格中的语义分析程序;语义分析程序将增加数据类型和其他信息;优化阶段和代码生成阶段也将利用由符号表提供的信息选出恰当的代码。因为对符号表的访问如此频繁,所以插入、删除和访问操作都必须比常规操作更有效。尽管可以使用各种树的结构,但杂凑表却是达到这一要求的标准数据结构。有时在一个列表或栈中可使用若干个表格。   (4)常数表(literal table)   常数表的功能是存放在程序中用到的常量和字符串,因此快速插入和查找在常数表中也十分重要。但是,在其中却无需删除,这是因为它的数据全程应用于程序而且常量或字符串在该表中只出现一次。通过允许重复使用常量和字符串,常数表对于缩小程序在存储器中的大小显得非常重要。在代码生成器中也需要常数表来构造用于常数和在目标代码文件中输入数据定义的符号地址。   (5)中间代码(intermediate code)   根据中间代码的类型(例如三元式代码和P -代码)和优化的类型,该代码可以是文本串的数组、临时文本文件或是结构的连接列表。对于进行复杂优化的编译器,应特别注意选择允许简单重组的表示。   (6)临时文件(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的值留出一个空格,一旦知道该值后就会将该空格填上,利用临时文件可以很容易地做到这一点。

其他问题

  可从许多不同的角度来观察编译器的结构,还有其他一些可能的观点:编译器的 物理结构 、操作的顺序等等。由于编译器的结构对其可靠性、有效性、可用性以及可维护性都有很大的影响,所以编译器的编写者应熟悉尽可能多的有关编译器结构的观点。   (1)分析和综合   在这个观点中,已将分析源程序以计算其特性的编译器操作归为编译器的 分析 (analysis)部分,而将生成翻译代码时所涉及到的操作称作编译器的综合(synthesis )部分。当然,词法分析、语法分析和语义分析均属于分析部分,而代码生成却是综合部分。在优化步骤中,分析和综合都有。分析正趋向于易懂和更具有数学性,而综合则要求更深的专业技术。因此,将分析步骤和综合步骤两者区分开来以便发生变化时互不影响是很有用的。   (2)前端和后端   本观点认为,将编译器分成了只依赖于源语言( 前端 (front end ))的操作和只依赖于目标语言(后端(back end ))的操作两部分。这与将其分成分析和综合两部分是类似的:扫描程序、分析程序和语义分析程序是前端,代码生成器是后端。但是一些优化分析可以依赖于目标语言,这样就是属于后端了,然而中间代码的综合却经常与目标语言无关,因此也就属于前端了。在理想情况下,编译器被严格地分成这两部分,而中间表示则作为其间的交流媒介。这一结构对于编译器的 可移植性 (portability)十分重要,此时设计的编译器既能改变源代码(它涉及到重写前端),又能改变目标代码(它还涉及到重写后端)。在实际中,这是很难做到的,而且称作可移植的编译器仍旧依赖于源语言和目标语言。其部分原因是程序设计语言和机器构造的快速发展以及根本性的变化,但是有效地保持移植一个新的目标语言所需的信息或使数据结构普遍地适合改变为一个新的源语言所需的信息却十分困难。然而人们不断分离前端和后端的努力会带来更方便的可移植性。   (3)遍   编译器发现,在生成代码之前多次处理整个源程序很方便。这些重复就是 ( pass)。首遍是从源中构造一个语法树或中间代码,在它之后的遍是由处理中间表示、向它增加信息、更换结构或生成不同的表示组成。遍可以和阶段相应,也可无关-遍中通常含有若干个阶段。实际上,根据语言的不同,编译器可以是一遍(one pass )-所有的阶段由一遍完成,其结果是编译得很好,但(通常)代码却不太有效。 Pascal语言和C 语言均允许单遍编译。(Modula - 2语言的结构则要求编译器至少有两遍)。大多数带有优化的编译器都需要超过一遍:典型的安排是将一遍用于 扫描 和分析,将另一遍用于语义分析和源代码层优化,第3遍用于代码生成和目标层的优化。更深层的优化则可能需要更多的遍:5遍、6遍、甚至8遍都是可能的。   (4)语言定义和编译器   程序设计语言的词法和语法结构通常用形式的术语指定,并使用正则表达式和上下文无关文法。但是,程序设计语言的语义通常仍然是由英语(或其他的自然语言)描述的。这些描述(与形式的词法及语法结构一起)一般是集中在一个语言参考手册(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 )的方法已经成为较为常用的方法,在函数编程共同体中尤为如此,但现在仍然没有一种可成为标准的方法。当语言有一个形式定义时,那么在理论上就有可能给出编译器与该定义一致的数学证明,但是由于这太难了,而几乎从未有人做过。无论怎样,运行时环境的结构和行为是尤其受到语言定义影响的编译器构造的一个方面。
 
 
 
程序设计语言—编译原理 (第3版31次印刷)
作者:陈火旺   出版社:国防工业出版社   出版时间:2010-2-4   ISBN:978-7-118-02207-0
内容介绍:  本书比较全面、系统地介绍了编译程序构造的一般原理和基本实现方法,内容包括词法分析、语法分析、属性文法与语法制导翻译、语义分析与中间代码产生、符号表与运行时存储空间组织、优化与目标代码生成、并行编译技术。与原教材相比,本书将编译技术的最新发展,例如属性文法、面向对象语言的编译技术、并行编译技术、编译程序自动构造工具等内容系统地融合到教材中;在语言背景方面,以C,Pascal 替代原教材中的FORTRAN和Algol;并在一些重要的章节中增加了必要的例题,以帮助读者理解和自学。本书可作为高等(理、工)院校计算机科学(或工程)专业的教材,或作为教师、研究生、高年级学生或软件工程技术人员的参考书。  

作者介绍:  陈火旺[2](1936 02.05 - 2008 02.02)中国工程院院士,国防科学技术大学计算机学院教授、博士生导师,于2008年2月2日因病医治无效,在长沙逝世,享年72岁。 
 
  
陈火旺是福建省安溪县人,1956年毕业于上海复旦大学数学系,同年加入中国共产党,留校任助教。曾在北京大学数理逻辑专业、英国国家物理所进修。1970年调长沙工学院(后改名国防科技大学),历任(电子)计算机系副教授、系副主任、教授、博士生导师、研究生院副院长。1990年被授予少将军衔。 1997年当选为中国工程院信息与电子工程学部院士。是武汉大学软件工程国家重点实验室学术委员会主任,国务院学位委员会计算机学科评议组成员,全国工科院校计算机专业教学指导委员会主任,国家“863计划”信息领域第一届专家委员会委员,中国软件行业协会副主任委员。1991年被授予国家有突出贡献中青年专家称号,同年获光华科学基金一等奖。长期从事计算机软件和人工智能等方面的教学和研究。建立了有限函数空间上的能行运算和能行连续泛函理论;主持国内第一个符号汇编语言和宏指令产生器的设计与实现;主持中国第一个FORTRAN编译程序的设计,获1978年全国科学大会奖;参与领导中国第一台巨型计算机银河I的研制,负责软件系统总体设计,获特等国防科技成果奖;主持国内最早的一个面向对象集成化软件开发环境GWOSE的研制,获国防科工委科技进步一等奖;领导自然语言处理的研究,研制成功英汉机器编译系统MATRIX,获全国优秀软件二等奖;在人工智能方面主持研制的非单调推理系统1993年获国防科工委科技进步一等奖。撰有《能行连续泛函》、《串行运算向量化》等论文、研究报告60余篇;主编有《数理逻辑与控制论》、《程序设计语言编译原理》、《程序设计方法学基础》等。  陈火旺院士为我国计算机软件与理论学科的建立和发展作出了贡献,为国家、军队和学校人才培养、科学研究作出了贡献。

0

阅读 (265) 评论 (0) 收藏 (1) 禁止转载 打印 举报
已投稿到:
排行榜
加载中,请稍候......
前一篇: 递归可枚举与编译文法

你可能感兴趣的:(优化,汇编,嵌入式,语言,fortran,编译器)