一、理解:编辑和解释(2)

按照最抽象的观点,高级语言里一个程序的编译和执行大致是下面的情况:
源程序————>编译器————>目标程序
输入————>目标程序————>输出
编译器将高级语言源程序翻译成与之等价的目标程序(典型情况就是机器语言程序),而后就隐退了。在随后的某个任意时刻,用户可以告诉操作系统去运行这个目标程序。编译器完全掌控着整个的编译过程,而目标程序在执行中完全控制着自己的活动。编译器本身也是一个机器语言程序,或许是由另外的某个高级语言程序编译而成的。在按照某种操作系统能理解的格式写入文件时,机器语言的程序常被称为目标代码。
 
实现高级语言的另一种方式称为解释:
源程序————>解释器————>输出
输入————>解释————>输出
与编译器不同,在应用程序执行期间,解释器一直守在旁边。事实上,这种执行过程是完全由解释器控制的。从效果上看,这种解释器实现了一台虚拟的机器,其“机器语言”就是这里的高级程序设计语言。解释器一次读入这种语言的或多或少的语句,按照它们应该工作的方式去执行相应的动作。
 
一般来说,与编译器相比,解释器可以带来更大的灵活性,它们能对程序做出更好的诊断(生成更好的错误消息)。由于这时的源代码是直接执行的,在解释器里可以包含一个优异的源代码层次的排错程序。采用这种方式也可以处理那样的一些语言,这些语言的程序里的一些基本特征,例如变量的大小和类型,甚至哪个名字引用哪个变量等等的情况,都依赖于执行时的实际输入。有些语言特征不用解释的方式几乎根本无法处理。举例说,在Lisp和Prolog里,一个程序可以为自己生成代码片段,并且可以随后就去执行它们。把有关程序实现的决策推迟到运行时才做,这种方式被称为迟约束。
 
与此相对应,编译方式通常能带来更好的性能。一般而言,在编译时可以做出的决策就是那些不需要到运行时再去做的决策。譬如说,如果编译器可以保证变量x总是被安排在位置49378,那么无论源程序里的任何地方引用x,它就可以生成出一条访问这个位置的机器语言指令。与此相反,解释器则需要在每次访问x时到一个表格里去查找它,以便找到它所对应的位置。由于一个程序(的最后版本)只编译一次,一般会执行许多次,这样节约的时间就可能很多了,特别是在遇到循环时,解释器将会在每次迭代中做许多不必要的工作。
 
编译和解释的概念之间的差异非常清楚,然而许多语言实现采用的是两者的混合形式。典型情况如下图所表示的:
 源程序————>编译器————>中间语言程序
 
 中间语言程序——>虚拟机——>输出
 输入——————>虚拟机——>输出
 
如果初始阶段的翻译器比较简单,我们就说这个语言是“解释的”。如果其中的翻译器很复杂,我们就说这一语言是“编译的”。现在两者的区分已经变得有些模糊了,因为“简单”和“复杂”都是修饰性的术语,也因为完全可能出现用一个编译器(复杂的翻译程序)生成代码,而后又由一个复杂的虚拟机(解释器)执行。对于最后这种情况,如果翻译器对程序做了彻底的分析(而不是只做某种“机械的”变换),而且有关的中间语言程序与源程序并没有很强的相似性,我们就还会说这个语言是编译的。这两种特性——彻底的分析和非平凡的变换——是刻画编译方式的标志性特征。
 
 在实际中可以看到不同实现策略的一个广泛的谱系。例如:
#大部分解释性语言都采用了一种初始的翻译器(一个预处理器),它去除程序里的注释和空白,将字符结组形成各种单词,例如关键字、标识符、数和符号。这个翻译器还可能采用与宏汇编器的类似方式展开各种缩写。最后,它还可能标记出各种高级语法结构,如循环和子程序。这种翻译器的目标可能是生成某种中间形式,该形式是源程序结构的某种镜像,但是可以更有效地解释。
在Basic的某些早期实现里,手册实际上建议程序员自己从程序里删除注释,以便改善其性能。这种实现就是纯粹的解释,它们会在每次执行程序中相应部分时反复地重新读入(而后忽略)其中的注释。这种解释器里没有初始翻译器。
¨        
#典型的Fortran实现很接近纯粹的编译,编译器把Fortran源码翻译到机器语言。然而,通常这些实现都要依靠一个子程序库的存在,这些子程序并不是原来程序里的一部分。子程序的例子包括各种数学函数(sin、cos、log等)和I/O。编译器需要依靠另一个称为连接器的程序,由它把所需的库例程结合到最后的程序里:
 Fortan程序——>编译器——>不完全的机器语言程序
 不完全的机器语言程序/库例程——>连接器——>机器语言
从某种观点看,我们可以认为这些库例程是硬件指令集合的扩充。因此可以认为这种编译器是为一部虚拟机生成代码,该虚拟机包含了硬件和库的功能。
 
从一种更字面角度出发,人们还是可以在Fortran的格式化输出例程里看到解释。Fortran允许程序员用format语句去控制输出在各栏对齐,控制浮点数的有效数字个数和所用的科学记法形式,控制是否包含前导的0等等。程序可以在运行中计算出所需格式,输出库例程里包含一个格式解释器。类似解释器也出现在C语言及其后裔的printf例程里。
 
#大部分编译器生成的是汇编语言而不是机器语言。采用这种方式更有利于排除程序里的错误。汇编语言更便于人们阅读,这样做也能将编译器隔离在机器语言文件格式的变化之外,新的操作系统有可能改变这种格式(此时就只需要改变汇编器,而它可以由许多编译器共用):
 源程序——>编译器——>汇编语言
 汇编语言——>汇编器——>机器语言
 
#C语言的编译器(以及在Unix系统中运行的许多其他语言的编译器)开始时用一个预处理器删除注释并展开宏。预处理器提供了一种条件编译功能,它能根据命令删除代码里的一些部分,使我们可以用同一份代码生成出一个程序的多个不同版本。
 源程序——>预处理器——>修改后的源程序
修改后的源程序——>编译器——>汇编语言
 
#基于早期AT&T编译器的C++实现生成的是C语言的中间程序,而不是汇编语言。
 源程序——>预处理器——>修改后的源程序
修改后的源程序——>C++编译器——>C代码
C代码——>C编译器——>汇编语言
这种C++编译器确实是真正的编译器,因为它对C++源程序做了完全的语法和语义分析,能生成除极少特殊情况之外的所有错误信息,使程序员可以在运行程序前看到它们。事实上,程序员通常根本意识不到背后还用了C编译器。只有确定生成出的C代码必定能通过第二轮编译而不会产生任何错误信息时,这个C++编译器才去调用相关的C编译器。
 
偶尔也可能听到有人把这种C++编译器说成是预处理器,原因是它所生成的高级语言输出还要经过编译。这种说法不对:编译器在工作中试图去“理解”源代码,而预处理器根本就不这样做。预处理器所执行的变换是基于简单的模式匹配,一般而言,由它们产生的输出在随后的翻译阶段里还可能出现错误信息。
 
#许多早期的Pascal实现是围绕着Niklaus Wirth提供的一集工具构造起来的,这些工具包括:
》一个用Pascal语言写出的编译器,它生成的P-代码作为输出,P代码是一种简单的基于堆栈的语言;
》同一个编译器,已经被翻译成了P-代码;
》一个P-代码解释器,也是用Pascal写出的。
要得到一个本地的Pascal实现,工具集的使用者只需把P-代码解释器(手工地)翻译到某种本地可以使用的语言。这一翻译并不是很困难的工作,因为解释器很小。通过在这个P-代码解释器上运行P-代码版本的编译器,人们就可以将任何Pascal程序编译到P-代码,而后这个程序就可以在解释器上运行了。为了得到更快速的实现,人们可以去修改Pascal版本的Pascal编译器,让它不是生成P-代码,而是生成本地可用的汇编语言或者机器语言代码(这是一项更困难些的工作)。而后这一编译器就可以“运行自己”,形成一种称为自举(bootstrap)的过程。
 
到了这一步,前面所用的P-代码解释器和P-代码版本的Pascal编译器都可以丢到一边了。更常见的情况是程序员仍然将这些工具留在手边。一个程序的P-代码版本通常比它的机器语言版本小很多,在20世纪70年代的机器上,节约内存和磁盘需求都是非常重要的事情。进一步说,正如本节开始时提出的,与编译器的输出相比,解释器通常能提供更好的运行时诊断功能。最后,解释器使程序可以在修改之后立即运行,而不必等到重新编译之后,这种特征在程序开发阶段也特别有价值。在许多最好的Pascal、C和其他命令式语言的程序设计环境,都包含了一个编译器和一个解释器。
¨        
有时我们也会看到某些支持大量迟约束的语言(例如Lisp、Prolog和Smalltalk等,这些语言传统上都是解释的)的编译器。这种编译器通常总是与一个解释器一起工作。编译器在做编译时尽可能做好所有它能做的事情,在没有办法的地方,它就生成出在运行时需要调用解释器的代码。
¨        
在某些情况下,程序设计系统会有意地把编译推迟到尽可能晚的时间去做。有一类例子出现在Lisp和Prolog实现里,那里可以在运行中调用编译器,把新生成的代码编译到机器语言。另一个例子出现在Java的实现里。Java语言规范定义了一种与机器无关的中间形式,称为字节码。字节码是发布Java程序的标准形式,它使程序很容易通过互联网传递后在各种不同的平台上运行。第一个Java实现基于一个字节码解释器,而更新(也更快)的实现里采用了一种即时编译器,在程序的具体执行之前,即时编译器将它由字节码编译为机器语言程序。
¨        
在许多机器上(特别是那些20世纪80年代设计的机器),编译层的指令并不真正翻译到硬件,而实际上是在一个解释器上运行的。这一解释器是用一种称为微代码(固件)的低级指令写成的,保存在机器的只读存储区里,由硬件执行。
 
上面这些例子已经清楚地说明,编译器未必就是把高级语言程序翻译到机器语言。现在产生C语言作为输出的编译器越来越普遍了,特别是那些原型编译器。说的再远一点,像TEX和troff之类的文本格式化程序实际上也是编译器,它们把高级的文档描述翻译到激光打印机或者照排机的命令序列(许多激光打印机本身就包含着一个Postscript页面描述语言的解释器)。数据库系统的查询语言处理器也是编译器,它们把SQL等语言翻译为基本文件操作原语。现在也有一些编译器将逻辑层的电路规范描述翻译到生产计算机芯片用的照排掩模。
 
 “编译”一词适用于一切从一种语言到另一种语言的非平凡的自动翻译,只要其中包含着对输入的意义分析。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/armman/archive/2007/04/14/1564656.aspx

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值