1.2.2 编译技术与开发环境
很多用户在被计算机强大而丰富的功能所折服的同时,也不禁会感到诧异!简单的计算机程序设计语言何以完成如此复杂的功能?这种与硬件之间的交流是如何完成的呢?笔者曾经使用C++语言在C++ Builder 6环境下编写了一个小的色彩编辑软件,图1-6给出了该软件运行的界面。
图1-6 使用C++语言编写的色彩编辑软件
这样一个漂亮的程序,是如何被编写出来的呢?它的本来面貌是否也如此花哨?为了让读者看清它的庐山真面目,笔者使用UltraEdit 32将上述色彩编辑软件的源文件显示出来,同时也将其对应的目标文件(*.obj)显示出来,如图1-7所示。显而易见,其目标文件是一些看似毫无规则的数字,这就是机器语言!只不过UltraEdit 32采用了十六进制显示方式,所以原来的二进制代码就变成了0到9,以及字母A到F的组合。关于数值的内容会在下一章讨论,这里就不再赘言了。
图1-7 源文件和目标文件
现在一定有两个问题困惑着读者!第一,编译程序是如何读懂源文件中的C++代码并把它转换为等价的目标文件(机器语言代码)的呢?第二,计算机是如何解读这些看似毫无规则的二进制序列(机器语言代码)的呢?第二个问题,会在本书的后续内容中进行研究。我们先来研究一下第一个问题,要回答这个问题,就必须要涉及计算机语言的编译技术。
如图1-8所示,一个典型的编译器通常由8个部分组成,即词法分析程序、语法分析程序、语义分析程序、中间代码生成程序、代码优化程序、目标代码生成程序、错误检查和处理程序及信息表管理程序。
图1-8 编译程序的逻辑结构
1.词法分析程序
编译器接收到的源文件应该是一组运用特定语言写成的文本,也就是说,待处理文件就是一个有限长度的字符串。这时首先需要通过词法分析程序对该输入进行预处理,这些预处理工作包括识别源程序中的各个基本语法单位——也就是单词;识别并删除无用的空白符、换行符等非实质性字符,以及注释;并进行词法检查,检查源文件中是否有不属于本语言的非法字符。
对于一种计算机语言来说,其单词常常划分为多种类型,主要有:
(1)标识符,它可能在源程序中充当函数名,也可能充当变量名等,甚至充当标号。例如,在C语言中,goto语句是用来标识跳转地址的标识符号。
(2)关键字,例如,C语言中的char、int、if、else、switch、case、while、for等都是关键字。
(3)运算符,也称操作符,以C语言为例,其中的运算符包括+、-、*、/、&等。
当然,还可能存在其他的类型,这里仅仅列举了几个大家比较熟悉的。通常在IDE中为代码进行语法着色的过程其实就是一个词法分析的过程。
2.语法分析程序
语法分析程序以词法分析程序所输出的以内部编码形式表示的单词序列作为输入。在相应程序设计语言的语法规则指导下,分析源程序是否是符合该语言语法规则的一段合法程序。通常的做法是尝试使用输入的单词序列来构造一棵完整的语法树(注意:这里的语法树只是一种逻辑上的概念,并非数据结构中的树型结构。通常所使用的尝试方法包括自顶向下和自下向上两种),如果语法树能够被正确地建立即表示该输入符合特定语言语法,否则语法就是错误的。
3.语义分析程序
语法分析旨在定义语言各语法成分的形式或结构,而语义分析则用来规定各语法成分的功能和含义。在计算机语言中,这种语法成分的功能和含义就是指程序运行时该执行怎样的操作或运算,以及数据元素的属性等。当然,这个过程中仍然需要进行语义检查以确保语义上的正确性。
4.中间代码生成程序
通常出于处理方便的考虑,更重要的是为了便于代码的优化,在语义分析之后编译器并不直接产生机器代码或者汇编代码,而是生成一种介于源代码与目标代码之间的中间代码。当然并不是所有的编译器都必须包含中间代码生成程序,它仅仅是一个可选项。但是某些特殊的语言则要求其编译器必须包含中间代码生成程序,例如Java语言。由于Java程序都需要在Java虚拟机上运行,因此经过javac程序处理生成的*.class文件就是一种中间代码文件。
5.代码优化程序
在生成目标代码之前,还需要对已经得到的“半成品”进行优化处理。优化处理的目的是为了提高目标代码的质量,这里的质量主要包括两方面的内容。首先,要尽量压缩目标程序所占用的空间,空间资源消耗的大小是程序质量高低的重要影响因素。其次,要尽可能加快目标程序的运行速度,也就是从时间角度进行优化。但要说明的是,在更多情况下,计算机系统中的时间和空间存在矛盾,要想求得时间与空间同时最优几乎不可能,因此优化的过程中需要综合考虑,注意权衡,具体方案都依据实际情况而定。
6.目标代码生成程序
编译过程的最终结果就是以目标代码写成的目标文件。在经过上述一系列加工之后,最终将由目标代码生成程序来输出目标文件。目标代码生成程序从前几个阶段得到的信息包括中间代码或优化后的中间代码,以及带有存储信息的符号表。这一阶段的主要任务是把源程序的中间代码变换成依赖于具体机器的等价的目标代码。对一个代码生成器最重要的评价标准是它能产生正确的代码。为了产生较优的代码,需要合理地使用寄存器,因为指令对于寄存器的操作常常要比对存储单元的操作快且指令短。因此,目标代码生成程序中最重要的部分就是寄存器的分配和目标代码的生成算法。
7.错误检查和处理程序
程序员在编写程序过程中出现错误在所难免。因此,当一个编译器接收到一个含有错误的源文件时,它应该能够主动地发现错误,定位错误,并给程序员提供一些建议以帮助程序员快速发现并修正错误。需要说明的是,错误检查和处理程序同上述前6个模块不同,它并不是一个单独存在的模块,它是贯穿于整个编译过程各个阶段的,也可以说在不同的处理阶段,各模块都包含有各自的错误检查处理程序,因此独立完整的错误检查处理程序其实并不存在。
8.信息表管理程序
信息表管理程序与错误检查和处理程序一样是贯穿于整个编译过程的,它负责建立、填写和查找等一系列表格工作。表格的作用是记录源程序的各类信息和编译各阶段的进展情况,编译的每个阶段所需信息多数都从表格中读取,产生的中间结果都记录在相应的表格中。例如,在词法分析阶段,词法分析程序读到一个符号“+”,此时它就会去查相应的语言符号表,以确定该符号是否是语言体系中的合法符号,并给出该符号的一些具体属性和信息。
可以说整个编译过程就是造表、查表的工作过程。因此,信息表管理程序也并非一个独立的“表格管理程序”,它只是表明编译程序具有的表格管理功能。
编译原理是计算机学科中一门非常重要也非常底层的学科,编译程序本身就是一个复杂的翻译系统,它的过程复杂、实现更加复杂,因此编译原理被认为是计算机科学众多课程中最晦涩、最难懂的一门。编译工作在各个阶段中都要涉及大量的数据结构和算法,而且每种算法的执行步骤都有可能达到数页之多,令人望而生畏。但其实如果耐心去思考、去琢磨,或许它并没有想象中的那么难懂,毕竟编译器也都是人写的。如果给初学编译原理的人提个建议的话,那就是要注重实践,如果能够在学完编译器6大模块中的每一个模块之后,都尝试着自己来实现一下的话,枯燥难懂的知识可能就变得明晰简单多了。当然,如果只是要做一个程序员,也不一定非要对编译原理的每个细节都了如指掌(如果你不是开发编译器的程序员的话),就本书所述的内容而言,读者能够对编译过程的各阶段所完成的任务有个形象的认识和理解就足够了。
由于计算机语言丰富多样,因此相应的编译器也种类繁多,其中早期比较著名的编译器包括Borland公司推出的Turbo Pascal和Turbo C等。但随着计算机技术的飞速发展,软件应用领域不断扩大,具体开发项目的规模也在不断激增。为了顺应时代的发展,一方面,科学家们从语言本身做了很大的改进,从面向机器的语言到面向人的语言、从非结构化的语言到结构化的语言、从面向过程的语言到面向对象的语言,这些语言的更新和发展都为整个软件产业的发展和时代的进步注入了源源不断的动力和能量。另一方面,编译器也在发生变化。为了最大程度地提高程序员的开发效率,人们在编译器的基础上进行了很多人性化的改进,并增加了诸如代码编辑、语法着色等具体的功能,于是经过一番包装之后,朴素的编译器就变成了花枝招展的集成化开发环境(IDE)了。前面所讲的Turbo Pascal就是世界上首批发布的DOS开发环境下的开发工具之一,它的出现使个人计算机应用程序的商用开发成为可能。
目前,可视化集成开发环境极大程度地提高了程序员的开发效率,为项目的顺利进行提供了有力保障,其中比较常用的可视化集成开发环境包括微软公司著名的Visual Studio系列。截至本书完稿之时,微软公司的Visual Studio 2008已经正式发布。如图1-9所示即为Visual Studio系列中Visual C++ 2005软件的界面。
另外一套著名的可视化集成开发环境就是由Borland公司开发的RAD Studio系列,其代表产品主要包括Delphi、C++ Builder、C# Builder和JBuilder等。
Borland软件公司(Borland Software Corporation)最初由Philippe Kahn于1983年创立,其总部位于美国的加利福尼亚州。自成立以来,Borland 软件公司一直是平台独立的软件开发与分发解决方案领域的领导者。Borland 公司的第一个产品就是大名鼎鼎的Turbo Pascal。Borland发布Turbo Pascal之前,Microsoft公司一直是编程语言领域的市场领先者;但在Turbo Pascal发布之后,由于其集成开发环境(IDE)比Microsoft公司的基于命令行界面的编译器及解释器更加方便好用,迫使微软不得不在该方面进行革新,正如我们所知道的,微软的确也推出了许多令人叹为观止的作品,之后两者在该领域的竞争就一直持续到今天。
图1-9 Visual C++ 2005软件的界面
在20世纪80年代,Borland公司曾经有过一段非常辉煌的业绩。但是好景不长,到了20世纪90年代,Borland公司进入了一个起伏跌宕的阶段。
20世纪90年代中期,Borland不仅在数据库软件方面逊色于微软,就连一开始一直处于优势地位的C++开发工具也在与微软竞争中败下阵来,尤其是Borland C++和Visual C++的竞争几乎是一败涂地。当然除了产品本身的一些问题外,一些分析人士还认为Philippe Kahn在资源分配方面欠缺考虑,由于他将公司的资源投入到了太多的项目上,以至于同Microsoft在多个层面展开竞争。此外,20世纪90年代中期,个人计算机软硬件产品的热销使得很多决策者感到困惑。正在Borland踌躇迷茫时,其竞争对手如微软等却果断地抓住了时机,为日后的发展奠定了坚实的基础。Kahn在1994年离开了Borland公司,此后Borland公司的发展更是摇曳不定。
但值得一提的是,在Anders Hejlsberg(Turbo Pascal的作者)的领导下,Borland公司于1995年发布了Delphi 1快速应用开发(RAD)环境,这个产品到现在依然是Borland公司最成功、最引以为傲的经典之作。
但之后的几年,Borland公司仍然是在经营的谷底挣扎度日。长久以来,Borland公司都缺乏一个明确的产品战略。在Kahn 离开Borland 公司之后,紧跟在他后面的是一连串的几个管理团队,而且每个都有自己的战略。1998年4月29日,Borland 从桌面软件应用产品提供商转变为提供安装和管理企业软件系统产品及服务的提供商,将业务重心集中于企业应用软件开发,并且将名字变更为Inprise 公司,旨在告诉人们它的转型方向和策略。不幸的是,所有这些战略最终都没能使Borland公司摆脱惨淡经营的窘境。
1999年,Dale L Fuller 取代Del Yokam 成为Borland公司的新CEO。Dale L Fuller挽救了当时处于破产边缘的Borland 公司,在他的努力下,2000年,Borland扭亏为盈,并在随后两年里,保持了持续盈利。2001年1月,Borland重新从Inprise改名为Borland,这在当时是一件非常令人兴奋的事情,尤其是Borland公司中的一些元老们都非常怀念这个令人振奋的名字。但好景不长,Borland 公司再度陷入增长困境,2005年,由于财务与商务错误,Dale L Fuller辞去了CEO职务。
2005年11月9日,Borland 公司宣布Tod Nielsen 接任公司CEO的职务。2005年10月, Borland 收购了Legadero,将它的IT Management and Governance (ITM&G)包Tempo加入到Borland的产品线。2006年2月8日,Borland 宣布剥离其IDE部门,包括Borland Delphi、JBuilder与InterBase。同时宣布即将收购Segue Softwar这家软件测试与质量工具制造商,把精力集中于应用产品管理(ALM)。并打算将剥离出去的部分重组成CodeGear公司,这一目标于2006年11月14日实现,当日Borland 宣布将开发工具组剥离出去,组成一个全资子公司集中于提升开发人员的生产效率。新建立的公司CodeGear将负责先前与Borland 集成开发环境业务相关的4种主要产品线的发展。CodeGear是Borland新成立的开发者工具业务公司,是一家出售其开发者工具的附属公司。目前,CodeGear旗下的产品包括:Jbuilder、Developer Studio系列产品(Delphi、C++ Builder、C# Builder等)及InterBase等。至少在目前来看,Borland公司将IDE部门剥离的决策的确带来了很大的积极作用。
CodeGear 公司的成立给原本处于低靡的IDE产品开发注入了强大的动力。许久都未升级的C++ Builder 6也迎来了其革命性的新版本C++ Builder 2005,并在此之后接连推出了多个新版本,如C++ Builder 2006、C++ Builder 2007。到目前为止,CodeGear已经推出了其全线产品的新版本C++ Builder 2009、Delphi 2009和JBuilder 2008等。