扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
1.程序是什么?
简言之,程序是用来描述、指导计算机完成我们需要完成的任务的代码(广义的程序还包括相关的软件运行所需的图片、动画、声音等资源)。比如,我要向屏幕打印一个字母“A”;或者要让CPU为我计算2+3的值,等等。由于时代、思维方式、编程任务以及个人喜好等因素的不同,出现了许许多多的描述方式,对这些不同的描述方式,我们就将它们区分开来,称之为不同的语言。例如前面所说的,要向屏幕打印一个“A”字符,在BASIC语言中的对应描述是:
PRINT “A”
而下面则是Pascal下的描述:
writeln(‘A’);
再给出C语言的:
printf(“A”);
而C++则更倾向于下面的表述:
cout << “A”;
下面是Java:
System.out.print("A ");
以上,仅从输出“A”字符这样一个最基本的实现就可以看出,不同的语言之间往往有相当明显的差别。当然,刚才我们所“领略”到的只是形式上的区别,而其背后的思想差异才是最重要的。
此外,程序语言还有高、低级之分。噢,不要望文生义地认为所谓的“低级”语言实现的功能会比“高级”语言实现的要少——恰恰相反,最“低级”的语言可以实现所有的功能,而那些太过“高级”的语言反而可能会有这样那样的限制。原来,这里的高、低并不是指语言功能强大与否,而是指语言描述能力,或者说是语言与人类自然语言(思维方式)的接近程度。实际上,计算机唯一能“懂”的是最“低级”的机器语言,说白了就是像0101这样的二进制指令代码。但成天用0101来写指令实在太累,也很容易出错,所以就出现了相对“高级”一点的汇编语言,它和机器语言基本上可以做到一一对应,但它的写法是充满了类似于mov、jmp这样的助记符,而不是令人头大的0101们。所以实在是方便了不少。当汇编代码写好之后,再由一个可以自动转换的代码的程序来将它们转换成为可执行的二进制代码。
虽然汇编语言相对于机器语言来说着实是亲切不少,但由于它很大程度上毕竟是机器语言的简单对应式的替换,因此在编写一些复杂的大型程序时尤其烦琐;而由于其表达能力过于简单,所以错误更是难以避免;此外,由于汇编语言所对应的机器语言实际上可以看作是一系列对硬件与系统较底层的操作,所以一旦换用不同的系统平台或者机器,几乎必须要进行修改甚至重新编写。即使是像前面输出“A”这样一个简单的操作,在Windows和Unix下就要分别写两个不同的汇编版本。其实我们所要求的功能是一样的,但汇编语言的抽象程度太低了,所以体现不出来。为此,人们又开发出各种高级程序语言,像Fortran、Ada、LISP、BASIC、Pascal、C/C++以及近年来出现的Java、C#等等。由于它们的抽象度比较高,源代码无须与硬件、系统底层操作对应,所以移植性比汇编要好得多,理想的情况下甚至不必为不同的系统平台或者机器改动源代码。
自此,程序就分为了两类,一类称为“源程序”,就是程序员用各种相对高级一些的语言编写的人们易于读懂的的代码,包括前面提及的汇编代码,以及其它所有的高级程序语言编写的代码;另一类则称为“可执行程序”,就是由汇编代码或者高级语言代码经过程序的转换后最终得到的二进制程序,我们的电脑可以直接识别并执行它,所以叫“可执行”。与之相应的概念,还有“源代码”/“可执行代码”、“源文件”/“可执行文件”等等。
你一定容易理解,“源代码”就是指以汇编或者高级语言所编写的代码,这些代码我们将它们以文件的形式保存起来,就成为源文件。大多数的源文件都是以最简单的文本形式进行存储的,和我们常见的.txt文件没有区别,只不过为了表明它是源文件,所以通常会起不同的扩展名,像BASIC语言为.bas,C语言为.c等等。因此,你可以用记事本这样的简单的文本编辑器对它们进行创建或者编辑。
2.从源代码到执行
前面说过,计算机只认得二进制的可执行代码,而我们更乐意使用汇编语言和各种高级语言。那么,我们编写的这些源程序最终是如何被执行的呢?
对于汇编语言,前面已经提到过一个类似于转换器的程序,它可以把我们写的汇编语言“翻译”成机器语言,然后保存在一个它生成的程序文件中,就得到了我们所要的可执行程序。
而高级语言家族则非常丰富,所以对这个问题的解决也是八仙过海,各显神通。大体分来,也不外乎两大类:编译型和解释型。
我们先看看编译型,其实它和前面的汇编语言是一样的:也是有一个负责翻译的程序来对我们的源代码进行转换,生成相对应的可执行代码。这个过程说得专业一点,就称为编译(Compile),而负责编译的程序自然就称为编译器(Compiler)。如果我们写的程序代码都包含在一个源文件中,那么通常编译之后就会直接生成一个可执行文件,我们就可以直接运行了。但对于一个比较复杂的项目,为了方便管理,我们通常把代码分散在各个源文件中,作为不同的模块来组织。这时编译各个文件时就会生成目标文件(Object file)而不是前面说的可执行文件。一般一个源文件的编译都会对应一个目标文件。这些目标文件里的内容基本上已经是可执行代码了,但由于只是整个项目的一部分,所以我们还不能直接运行。待所有的源文件的编译都大功告成,我们就可以最后把这些半成品的目标文件“打包”成一个可执行文件了,这个工作由另一个程序负责完成,由于此过程好像是把包含可执行代码的目标文件连接装配起来,所以又称为链接(Link),而负责链接的程序就叫……就叫链接程序(Linker)。链接程序除了链接目标文件外,可能还有各种资源,像图标文件啊、声音文件啊什么的,还要负责去除目标文件之间的冗余重复代码,等等,所以……也是挺累的。链接完成之后,一般就可以得到我们想要的可执行文件了。
上面我们大概地介绍了编译型语言的特点,现在再看看解释型。噢,从字面上看,“编译”和“解释”的确都有“翻译”的意思,它们的区别则在于翻译的时机安排不大一样。打个比方:假如你打算阅读一本外文书,而你不知道这门外语,那么你可以找一名翻译,给他足够的时间让他从头到尾把整本书翻译好,然后把书的母语版交给你阅读;或者,你也立刻让这名翻译辅助你阅读,让他一句一句给你翻译,如果你想往回看某个章节,他也得重新给你翻译。
两种方式,前者就相当于我们刚才所说的编译型:一次把所有的代码转换成机器语言,然后写成可执行文件;而后者就相当于我们要说的解释型:在程序运行的前一刻,还只有源程序而没有可执行程序;而程序每执行到源程序的某一条指令,则会有一个称之为解释程序的外壳程序将源代码转换成二进制代码以供执行,总言之,就是不断地解释、执行、解释、执行……所以,解释型程序是离不开解释程序的。像早期的BASIC就是一门经典的解释型语言,要执行BASIC程序,就得进入BASIC环境,然后才能加载程序源文件、运行。解释型程序中,由于程序总是以源代码的形式出现,因此只要有相应的解释器,移植几乎不成问题。编译型程序虽然源代码也可以移植,但前提是必须针对不同的系统分别进行编译,对于复杂的工程来说,的确是一件不小的时间消耗,况且很可能一些细节的地方还是要修改源代码。而且,解释型程序省却了编译的步骤,修改调试也非常方便,编辑完毕之后即可立即运行,不必像编译型程序一样每次进行小小改动都要耐心等待漫长的Compiling…Linking…这样的编译链接过程。不过凡事有利有弊,由于解释型程序是将编译的过程放到执行过程中,这就决定了解释型程序注定要比编译型慢上一大截,像几百倍的速度差距也是不足为奇的。
编译型与解释型,两者各有利弊。前者由于程序执行速度快,同等条件下对系统要求较低,因此像开发操作系统、大型应用程序、数据库系统等时都采用它,像C/C++、Pascal/Object Pascal(Delphi)、VB等基本都可视为编译语言,而一些网页脚本、服务器脚本及辅助开发接口这样的对速度要求不高、对不同系统平台间的兼容性有一定要求的程序则通常使用解释性语言,如Java、JavaScript、VBScript、Perl、Python等等。
但既然编译型与解释型各有优缺点又相互对立,所以一批新兴的语言都有把两者折衷起来的趋势,例如Java语言虽然比较接近解释型语言的特征,但在执行之前已经预先进行一次预编译,生成的代码是介于机器码和Java源代码之间的中介代码,运行的时候则由JVM(Java的虚拟机平台,可视为解释器)解释执行。它既保留了源代码的高抽象、可移植的特点,又已经完成了对源代码的大部分预编译工作,所以执行起来比“纯解释型”程序要快许多。而像VB6(或者以前版本)、C#这样的语言,虽然表面上看生成的是.exe可执行程序文件,但VB6编译之后实际生成的也是一种中介码,只不过编译器在前面安插了一段自动调用某个外部解释器的代码(该解释程序独立于用户编写的程序,存放于系统的某个DLL文件中,所有以VB6编译生成的可执行程序都要用到它),以解释执行实际的程序体。C#(以及其它.net的语言编译器)则是生成.net目标代码,实际执行时则由.net解释系统(就像JVM一样,也是一个虚拟机平台)进行执行。当然.net目标代码已经相当“低级”,比较接近机器语言了,所以仍将其视为编译语言,而且其可移植程度也没有Java号称的这么强大,Java号称是“一次编译,到处执行”,而.net则是“一次编码,到处编译”。呵呵,当然这些都是题外话了。总之,随着设计技术与硬件的不断发展,编译型与解释型两种方式的界限正在不断变得模糊。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者