1.1什么叫编译程序
1.1.1程序设计语言的发展
机器语言、汇编语言、高级语言
随着计算机应用需求的不断增长,人们希望能有功能更强、抽象级别更高的语言来支持程序设计,于是就产生了面向各类应用的程序设计语言。这些语言的共同特征是便于人类的理解与使用,因此被称为面向人类的语言或高级语言。表1.1列出了几种面向机器和面向人类的语言及其表现形式。
表1.1 面向机器和面向人类语言举例
1.通用程序设计语言
通用程序设计语言是继汇编语言之后发展起来的应用最广的一类语言,如人们常用的FORTRAN、Pascal、C/C++、Ada83/Ada95、Java等语言。这类语言的特征是:语言结构符合人类的思维特征,如直接使用表达式进行数学运算;具有很高抽象程度,如引入过程与类等机制;程序设计中强调逻辑过程,即程序员要考虑事情的前因后果,不但要设计做什么,还要考虑怎么做,如条件或循环的判断等。
2.数据查询语言
与通用程序设计语言相比,数据查询语言的抽象程度更高,它只要求程序员具有清晰的逻辑思维能力,设计好做什么,而忽略怎么做这样的实现细节,从而使得对大量复杂数据的处理变得轻松简单。
3.形式化描述语言
形式化描述语言的代表之一是编译器构造中常用的工具YACC的语言。这类语言的核心部分是基于数学基础的产生式,设计人员只需利用产生式描述语言结构的文法,就可以构造出识别该语言结构的识别器。
4.其他面向特定应用领域的语言
随着计算机应用领域的不断拓展,先后出现了多种面向特定应用领域的高级语言,如面向互联网应用的HTML、XML,面向计算机辅助设计的MATLAB,面向集成电路设计的VHDL、Verilog,面向虚拟现实的VRML等等。这些形形色色、多不胜数的计算机语言推动了计算机应用的飞速发展,使得计算机成为人类生活中不可缺少的重要部分。
1.1.2 语言之间的翻译
1、“编译”:将高级语言编写的程序通过一个翻译程序的加工,使之转变为与其等价的目标语言程序,这种翻译程序,称它为“编译程序”。
2、编译系统:编译程序与运行系统合称编译系统
高级语言之间的翻译,一般被称为转换,如FORTRAN到Ada的转换等,或者被称为预处理,如SQL到C/C++的预处理等。
高级语言可以直接翻译成机器语言,也可以翻译成汇编语言,这两个翻译过程被称为编译。从汇编语言到机器语言的翻译被称为汇编。
运行编译程序的计算机称宿主机,运行编译程序所产生目标代码的计算机称目标机。若一个编译程序产生不同于其宿主机的机器代码,则称它为交叉编译程序。上述这些翻译模式一般被认为是正向工程。
在一些特定情况下需要逆向工程,如把机器语言翻译成汇编语言,或者把汇编语言翻译成高级语言,分别称它们为反汇编和反编译。值得一提的是,反编译是一件十分困难的事情。
图1.1 语言之间的翻译模式
图1.1给出了一些常见的语言之间的翻译模式。在图1.1中,语言分为三个层次:高级语言、汇编语言、机器语言。设分别有两个高级语言L1和L2,两个汇编语言A1和A2,以及两个机器语言M1和M2。
3、 编译器与解释器
编译器(Compiler)一词是Grace Murray Hopper在20世纪50年代初提出来的,而被公认为最早的编译器是50年代末研制的FORTRAN编译器。
从用户的观点来看,编译器是一个黑盒子,如图1.2(a)所示。源程序的翻译和翻译后程序的运行是两个独立的不同阶段。首先是编译阶段,用户输入源程序,经过编译器的处理,生成目标程序。然后是目标程序的运行阶段,根据目标程序的要求进行适当的数据输入,最终得到运行结果。
解释器采用另一种方式翻译源程序。它不像编译器那样,把源程序的翻译和目标程序的运行分割开来,而是把翻译和运行结合在一起进行,翻译一段源程序,紧接着就执行它。这种方式被称为解释。在计算机应用中,凡是可以采用编译方式的地方,几乎都可以采用解释的方式,图1.2(b)是一个解释器的工作模型。
假设有源程序:
read(x); write("x=",x);
则编译器的输入是此源程序。目标程序的输入如果是3,则输出是x=3。
而对于解释器,则输入端既包括上述源程序,又包括3,其输出同样是x=3。
可以看出,编译器的工作相当于在翻译一本原著,计算机运行编译后的目标程序,相当于阅读一本译著,原著(或原作者)和译著者并不在场,主角是译著。
而解释器的工作相当于在进行同声翻译,计算机运行解释器,相当于我们直接通过翻译听外宾讲话,外宾和翻译均需到场,主角是翻译。
图1.2 编译器与解释器工作方式的对比
(a)编译器的工作方式;(b)解释器的工作方式
解释器与编译器的主要区别在于:运行目标程序时的控制权
在解释器而不在目标程序。
解释器有以下两个优点:
(1) 具有较好的动态特性:运行时,由于源程序也参与其中,因此可修改,且可提供较好的出错诊断,从而为用户提供数据对象的类型可以动态改变,并允许用户对源程序进了交互式的跟踪调试功能。
(2) 简单、小巧
http://www.turbozv.com/read.php?511.1.3基本术语
1.源程序(Source program)
2.目标程序(Objectcode)
3.翻译程序(Translator)
4.汇编程序(Assembler)
5.编译程序(Compiler)
6.解释程序(Interpreter)
1.2、编译过程概述
编译过程与外文翻译类似。
编译程序的工作过程可划分五个阶段:
1、词法分析
2、语法分析
3、语义分析和中间代码生成
4、代码优化
5、目标代码生成
1.词法分析(LexicalAnalysis):
从左到右一个字符一个字符的读入源程序,对构成源程序的字符串进行扫描和分解,从而识别出一个个单词(也称单词符号或简称符号)
例如某程序片段如下:
var
sum, first, count :real;
begin
sum := first + count * 10 ;
end.
词法分析阶段通过对该程序片段的扫描、分解,将会把组成这段程序的字符识别为如下单词序列:
1.保留字 var 2.标识符 sum
3.逗号 , 4.标识符 first
5.逗号 , 6.标识符 count
7.冒号 : 8.保留字 real
9.分号 ; 10.保留字 begin
11.标识符 sum 12.赋值号 :=
13.标识符 first 14.加号 +
15.标识符 count 16.乘号 *
17.整数 10 18.保留字 end
19.界符 .
我们用id1,id2和id3分别表示sum,first和count三个标识符的内部形式,那么分析后,sum:= first + count * 10
可表示为:id1:= id2 + id3 * 10
2.语法分析(SyntaxAnalysis):
在词法分析的基础上将单词序列分解成各类语法单位,如“表达式”,“语句”,“分程序”,“程序” 等等,例如,上例中的赋值语句通过语法分析可用以下语法树(Parsetree)表示出来。
3.语义分析(SyntacticAnalysis):
语义分析是在语法分析程序确定出语法单位后,审查有无语义错误,并为代码生成阶段收集类型信息。
(1)语法分析和语义分析是以密切合作的方式工作的。
(2) 语义分析的重要工作之一是进行语义检查,并为代码生成收集类型信息(类型检查、变量是否声明、类型是否一致、变量是否已有值等)。
4.中间代码生成:(Generationof intermediate code)
完成语法分析和语义处理工作后,编译程序将源程序变成一种内部表示形式,这种内部表示形式叫做中间语言或称中间代码,它是一种结构简单、含义明确的记号系统。
例如,四元式的形式为:
(算符,运算对象1,运算对象2,结果)
对于赋值语句:position:= initial + rate * 60
可以生成如下所示的四元式:
(IntToReal , 60 , — , t1)
( * , id3 , t1 , t2 )
(+ , id2 , t2 , t3)
(:= , t3 , — , id1 )
5.代码优化(Optimizationof code):
为了使生成的目标代码更为高效,可以对产生的中间代码进行变换或进行改造,这就是代码的优化。
代码优化工作可以在不同的编译阶段进行,其中对中间代码的优化尤其重要。
6.代码生成(Generationof code):
目标代码生成阶段的任务就是是把中间代码变换成特定机器上的绝对指令代码或可重定位的指令代码或汇编指令代码。
对于上面所述源程序:position:=initial+rate*60对应的中间代码可生成下面的某汇编代码:
( * , id3 , 60.0 , t1 )
( + , id2 , t1 , id1)
MOVF id3, R2
MULF 60.0, R2
MOVF id2, R1
ADDF R1, R2
MOV R1, id1
目标代码生成是编译器的最后一个阶段。在生成目标代码时要考虑以下几个问题:计算机的系统结构、指令系统、寄存器的分配以及内存的组织等。
编译器生成的目标程序代码可以有多种形式。
(1) 汇编语言形式(Assembly Language Format):编译器生成汇编语言形式的代码序列。一般来讲,生成汇编指令代码比生成二进制代码序列在处理上要简单且易读,而且,由于汇编语言仍然是符号形式的,所以特别便于实现交叉编译。它的弱点是编译之后还要经过一次汇编。
(2)可重定位二进制代码形式(Relocatable Binary Format):
这实际上是编译器常采用的一种目标代码。编译器生成二进制代码模块,模块内地址以模块首地址相对寻址,经过链接程序进行链接。链接时还需把程序中所引用的预定义标准例程和其它已编译过的模块包括进来,最后形成一个可直接运行的代码序列。
(3)内存形式(Memory-ImageFormat):
编译器生成的代码序列直接被装入原编译器所在的位置并被立即执行,也就是编译后马上运行。这类形式在英文中也被称为Load-and-Go。由于这种形式不生成以文件形式存放在磁盘上的目标代码,也没有被链接的过程,因而这种形式特别适合初学者或在程序的调试阶段使用。它的弱点是运行一次就需要编译一次。
由于这三种形式各有其它形式无法替代的特点,有些编译器同时提供这三种或者其中两种形式,用户可以根据需要选择使用。
7.符号表(Symbol Table)
主要应用于以下情况:
收集符号的属性信息:当分析到标识符的说明部分时,收集有关标识符的属性,并存于符号表中;
作为进行语法的合法性检查的依据:同一个标识符可能在程序的不同地方出现,有关符号的属性是在不同的情况下收集的,需要检查标识符在上下文中的一致性和合法性,而符号表正是进行这种检查的依据;
作为目标代码生成阶段地址分配的依据:每个变量在目标代码生成时都需要确定其对应的存储地址,编译程序在完成了对变量的地址分配后,将其存于符号表中,可以通过符号表获取每个变量对应的存储地址。
8.错误检测(Detectionof Errors)
源程序中常常存在各种各样的错误:即使对一个熟练的程序员来说,也很难做到一次上机就能得到预期的结果。所以,对于源程序错误处理的程度,是作为衡量一个编译程序良莠的主要标准之一。一个好的编泽程序应将源程序中的错误尽可能多地诊察出来,如果有可能的话,对错误进行适当的校正。这样就可以使程序员及时发现和改正错误,从而减少上机编译的次数,以节约机时,节省费用;
错误检测处理与符号表管理工作一样,贯穿整个编译过程。
二、编译阶段的组合 ——前端和后端
前端(Front-End)——与目标机无关的部分
包括分析部分(词法、语法、语义分析)、中间代码生成、与目标己无关的优化以及这部分的符号表管理错误处理
后端(Back-End)——与目标机有关部分
包括代码生成、与目标机有关的优化以及这部分的符号表管理和错误处理工作
不同的前端和不同的后端相互配合可以得到不同的编译器: