5 目标模块(包括系统提供的库模块)连接 一起,确定程序变量(或常数)在主存中的位置 装人内存中指定的起始地址,使之成为一个可以运行的绝对指令代码程序。 上述编译过程的五个阶段是一种典型的分法。事实上,并非所有编译程序都分成过 五阶段。有些编译程序对优化没有什么要求,优化阶段就可省去。在某些情况下,为了加 快编译速度,中间代码产生阶段也可以去掉。有些最简单的编泽程序是在语法分析的同 时产生目标代码。但是,多数实用编译程序的工作过程大致都像上面所说的那五个阶段, 1.3 编译程序的结构 1.3.1编译程序总框 k述编译过程的五个阶段是编译程序工作时的动态特征。编译程序的结构可以按照 这五阶段的任务分模块进行设计。图11给出了编译程序总框。 程序 河法分析鲁 单词符号 话法分析得 语法单位 著义分析与中间代码产生舞】 处 中同代码 优化器 中间代码 目标代码生成帮 日标代码 图1.1编译程序总框 词法分析器,又称扫描器,输人源程序,进行词法分析,输出单词符号。 语法分析器,简称分析器,对单词符号串进行语法分析(根据语法规则进行推导或归 约),识别出各类语法单位,最终判断输人申是否构成语法上正确的“程序” 语义分析与中间代码产生器,按照语义规则对语法分析器归约出(或推导出)的语法 单位进行语义分析并把它们翻译成一定形式的中间代码。 有的编译程序在识别出各类语法单位后,构造并输出一棵表示语法结构的语法树,然 后,根据语法树进行语义分析和中间代码产生。还有许多编译程序在识别出语法单位后 并不真正构造语法树,而是调用相应的语义子程序。在这种编译程序中,扫描器、分析器 和中间代码产生器三者并非是截然分开的,而是相互穿插的
6 优化器,对中间代码讲行优化处理 目标代码生成器,把中间代码翻译成目标程序。 除了上述五个功能模块外,一个完整的编译程序还应包括“表格管理”和“出错处理 两部分。 1.3.2表格与表格普理 编译程序在工作过程中需要保持一系列的表格,以登记源程序的各类信息和编译各 阶段的进展状况。合理地设计和使用表格是编译程序构造的一个重要问题。在编译程序 使用的表格中,最重要的是符号表。它用来登记源程序中出现的每个名字以及名字的各 种属性。例如,一个名字是常量名、变量名,还是过程名等等;如果是变量名,它的类型是 什么、所占内存是多大、地址是什么等等。通常,编译程序在处理到名字的定义性出现时, 要把名字的各种属性填人到符号表中;当处理到名字的使用性出现时,要对名字的属性进 行查证 当扫描器识别出一个名字(标识符)后,它把该名字填人到符号表中。俱这时不能完 全确定名字的属性,它的各种属性要在后续的各阶段才能填人。例如,名字的类型等要在 语义分析时才能确定,而名字的地址可能要到目标代码生成才能确定 由此可见,编译各阶段都涉及到构造、查找或更新有关的表格。 1.3.3出错处理 个编译程序不仅应能对书写正确的程序进行翻译,而且应能对出现在源程序中的 错误进行处理。如果源程序有错误,编译程序应设法发现错误,把有关错误信息报告给用 户。这部分工作是由专门的 一组程序(叫做出错处理程序)完成的。 个好的编译程序应 能最大限度地发现源程序中的各种错误,准确地指出错误的性质和发生错误的地点,并且 能将错误所浩成的影南限制在尽可作小的范围内,使得源程岸的其余部分能珠续被绵还 下去,以便进一步发现其它可能的错误。如果不仅能够发现错误,而且还能自动校正错 误,那当然就更好了。但是,自动校正错误的代价是非常高的。 编译过程的每一阶段都可能检测出错误,其中,绝大多数错误可以在绮译的前三阶段 检测出来。源程序中的错误通常分为语法错误和语义错误两大类。语法错误是指源程序 中不符合语法(或词法)规则的错误,它们可在词法分析或语法分析时检测出来。例如,词 法分析阶段能够检测出“非法字符”之类的错误;语法分析阶段能够检测出诸如“括号不匹 配”、“缺少;”之类的错误。语义错误是指源程序中不符合语义规则的错误,这些错误 般在语义分析时检测出来,有的语义错误要在运行时才能检测出来。语义错误通常包括: 说明错误,作用域错误、类型不一致等等。关于错误检测和处理方法,我们将穿插在有关 章节介绍 1.3.4遍 前面介绍的编译过程的五个阶段仅仅是逻辑功能上的一种划分。具体实现时,受不 同源语言、设计要求、使用对象和计算机条件(如主存容量)的限制,往往将编译程序组织 为若干遗(P)。所谓“遍”就是对源程序或源程序的中间结果从头到尾扫描一次,并作
7 有关的加工处理,生成新的中间结果或目标程序。通常,每遍的工作由从外存上获得的前 一遍的中间结果开始(对于第一遍而言,从外存上获得源程序),完成它所含的有关工作之 后,再把结果记录于外存。既可以将几个不同阶段合为一遍,也可以把一个阶段的工作分 为若干遍。例如,词法分析这一阶段可以单独作为一遍,但更多的时候是把它与语法分析 合并为一避;为了便于处理,语法分析和语义分析与中间代码产生又常常合为一遍。在优 化要求很高时,往往还可把优化阶段分为若干遍来实现。 遗中包含若干阶段时,各阶段的工作是穿插进行的。例如,我们可以把词法分 析、语法分析及语义分析与中间代码产生这三阶段安排成一遍。这时,语法分析器处于核 心位置,当它在识别语法结构而需要下一单词符号时,它就调用词法分析器, 一日识别出 个语法单位时,它就调用中间代码产生器,完成相应的语义分析并产生相应的中间代 码。 个程岸穷音应分成几加别分,县与语言、设计要求、硬件设备等诸 有关的,因此难于统一划定。遍数多一点有个好处,即整个编译程序的逻辑结构可能清晰 一点。但遍数多势必增加输人输出所消耗的时间。因此,在主存可能的前提下,一般还 是遍数尽可能少一点为好。应当注意的是,并不是每种语言都可以用单遍编译程序实现。 1.3.5编译前端与后端 概念上,我们有时把编译程序划分为编译前端和编译后端。前端主要由与源语言有 关但与目标机无关的那些部分组成。这些部分通常包括词法分析、语法分析、语义分析与 中间代码产生,有的代码优化工作也可包括在前端。后端包括编译程序中与目标机有关 的那些部分,如与目标机有关的代码优化和目标代码生成等。通常,后端不依赖于源语言 而仅仅依横于中间语言。 可以取编译程序的前端,改写其后增以生成不同目标机上的相同语言的编译程序。 如果后端的设计是经过精心考虑的,那么后端的改写将用不了太大工作量,这样就可实现 编译程序的目标机改变。也可以设想将几种源语言编译成相同的中间语言,然后为不同 的前端配上相同的后端,这样就可为同一台机器生成不同语言的编译程序。然而,由于不 同语言存在某些微妙的区别,因此在这方面所取得的成果还非常有限 为了实现编译程序可改变目标机,通常需要有一种定义良好的中间语言支持。例如, 在著名的Ada程序设计环境APSE中,使用的是一种称为Diana的树形结构的中间语言 个Ada源程序通过前端编译转换为Dia中间代码,由编译后端把Diaa中间代码转换 为目标代码。编译前端与不同的编译后端以Diaa为界面,实现编译程序的目标机政变。 又如,在Java语言环境中,为了使编译后的程序从一个平台移到另一个平台执行,a v阳定义一种虚拟机代码 Byteco k。只要实际使用的操作平台上实现了执行Bte 的Java解释器,这个操作平台就可以执行各种Java程序。这就是所谓Java语言的操作平 台无关性。 1.4编译程序与程序设计环境 编译程序无疑是实现高级语言的一个最重要的工具。但支持程序设计人员进行程序
开发通常还需要一些其它的工具;如编辑程序连接程序,调试工具等等。编译程序与这 些程序设计工具一起构成所谓的程序设计环境。 在高级语言发展的早期,这些程序设计工具往往是独立的,缺乏整体性,而且也缺乏 对软件开发全生命周期的支持。随着软件技术的不断发展,现在人们越来趣衡向于构造 集成化的程序设计环境。一个集成化的程序设计环境的特点是,它将相互独立的程序设 计工具集成起来,以便为程序员提供完整的、一体化的支持,从而进一步提高程序开发效 率,改善程序质量。在一个好的集成化程序设计环境中,不仅包含丰富的程序设计工具 而且还支持程序设计方法学,支持程序开发的全生命周期。有代表性的集成化程序设计 环境有Ada语言程序设计环境APSE、LuSP语言程序设计环境NTERIJSP等。广大读者所 熟悉的Tubo Pascal、Turbo C、Visual C++等语言环境也都可认为是集成化的程序设计环 境。 下面以A血语言的程序设计环境APSE为例,介绍程序设计环境的基本构成和主要 工具 PSE是一个分层的程序设计环境,如图1.2所示。 APSE MAPSE 工具界面 KAPSE 主机 连程月 用户界霞 图1.2Ad程序设计环境 最内层(第0层)是宿主计算机系统,它包括硬件、宿主操作系统和其它支持软件 第-一层是核心APSE(KAPSE)。它包括环境数据库、通信及运行时支撑功能等。 第二层,最小ASE(MASE)。它包括了Ad程序开发及维护的基本工具,这些工具 包括编译程序、编辑程序、连接程序、调试程序、命令解释程序、配置管理程序、美化打印程 序、静态分析工具、动态分析工具等等。 第三层,APSE。在MAPSE外面再加上更广泛的工具就构成了完整的APSE。对这一 层没有精确规定工具的类型,它通常可以包括面向应用的工具和支持特定程序设计方法 的工具等。可以是支持需求分析、设计、实现、维护等软件开发全生命周期的工具
9 在一个程序设计环境中,编译程序起者中心的作用。连接程序,调试程序、程序分析 等工具的工作直接依赖于编译程序所产生的结果,而其它工具的构造也常常要用到编译 的原理、方法和技术。 1.5编译程序的生成 以前人们构造编译程序大多是用机器语言或汇编语言作工具的。为了充分发挥各种 不同硬件系统的效率,为了满足各种不同的具体要求,现在许多人仍然采用这种工具来构 造绵译程序(成编译程序的“核心部分)。但是,械来越多的人已经使用高级语言作工具 来编译程序。因为,这样可以大大节省程序设计时间,而且所构造出来的编译程序易于阅 读、维护和移植。 为了便于说明.我们用一种T形图来表示源语言S、目标语言T和绵译程序实现语言 1之间的关系,如图13所示。 图13T形图 如果A机器上已有一个用A机器代码实现的某高级语言L的编译程序,则我们可以 用L语言编写另一种高级L2的编译程序,把写好的L2编译程序经过L编译程序编译后 就可得到A机器代码实现的L2编译程序,如图1.4所示。 2语言 A代码 L2墙言 A代用 A代A代 A代哥 图1,4用诉言编写编译程序 采用一种所谓的“移植“方法,我们可以利用A机器上已有的高级语言L编写一个能 够在B机器上运行的高级语言L的编译程序。做法是,先用L语言编写出在A机器上运 行的产生B机器代码的L编译程序源程序,然后把该源程序经过A机器上的L编译程序 编译后得到能在A机器上运行的产生B机器代码的编译程序,用这个编译程序再一次编 译上述编译程序源程序就得到了能在B机器上运行的产生B机器代码的编译程序。用丁 形图表示为图1.5所示。 我们还可以采用“自编译方式”产生编译程序。方法是,先对语言的核心部分构造 个小小的编译程序(可用低级语言实现),再以它为工具构造一个能够编译更多语言成分 的较大编译程序。如此扩展下去,就像滚雪球一样,越滚越大,最后形成人们所期望的整