浅谈汇编器、编译器和解释器

简单介绍一下编程方式的历史演变。前端

在计算机诞生不久的早期年代,硬件很是昂贵,而程序员比较廉价。这些廉价程序员甚至都没有“程序员”这个头衔,而且经常是由数学家或者电气工程师来充当这个角色的。早期的计算机被用来快速解决复杂的数学问题,因此数学家自然就适合“编程”工做。python

什么是程序?

首先来看一点背景知识。计算机本身是作不了任何事情的,它们的任何行为都须要程序来引导。你能够把程序当作是很是精确的菜谱,这种菜谱读取一个输入,而后生成对应的输出。菜谱里的各个步骤由操做数据的指令构成。听上去有点儿复杂,不过你或许知道下面这个语句是什么意思:linux

1 + 2 = 3
复制代码

其中的加号是“指令”,而数字 1 和 2 是数据。数学上的等号意味着等式两边的部分是“等价”的,不过在大部分编程语言中对变量使用等号是“赋值”的意思。若是计算机执行上面这个语句,它会把这个加法的结果(也就是“3”)储存在内存中的某个地方。git

计算机知道如何使用数字进行数学运算,以及如何在内存结构中移动数据。在这里就不对内存进行展开了,你只须要知道内存通常分为两大类:“速度快/空间小”和“速度慢/空间大”。CPU 寄存器的读写速度很是快,可是空间很是小,至关于一个速记便签。主存储器一般有很大的空间,可是读写速度就比寄存器差远了。在程序运行的时候,CPU 不断将它所须要用到的数据从主存储器挪动到寄存器,而后再把结果放回到主存储器。程序员

汇编器

当时的计算机很贵,而人力比较便宜。程序员须要耗费不少时间把手写的数学表达式翻译成计算机能够执行的指令。最初的计算机只有很是糟糕的用户界面,有些甚至只有前面板上的拨动开关。这些开关就表明一个内存“单元”里的一个个 “0” 和 “1”。程序员须要配置一个内存单元,选择好储存位置,而后把这个单元提交到内存里。这是一个既耗时又容易出错的过程。github

Programmers operate the ENIAC computer
Programmers operate the ENIAC computer

程序员Betty Jean Jennings (左) 和 Fran Bilas (右) 在操做 ENIAC 的主控制面板编程

后来有一名 电气工程师 认为本身的时间很宝贵,就写了一个程序,可以把人们能够读懂的“菜谱”同样的输入转换成计算机能够读懂的版本。这就是最初的“汇编器”,在当时引发了不小的争议。这些昂贵机器的主人不但愿把计算资源浪费在人们已经能作的任务上(虽然又慢又容易出错)。不过随着时间的推移,人们逐渐发现使用汇编器在速度和准确性上都胜于人工编写机器语言,而且计算机完成的“实际工做量”增长了。bash

尽管汇编器相比在机器面板上切换比特的状态已是很大的进步了,这种编程方式仍然很是专业。上面加法的例子在汇编语言中看起来差很少是这样的:架构

01 MOV R0, 1
02 MOV R1, 2
03 ADD R0, R1, R2
04 MOV 64, R0
05 STO R2, R0
复制代码

每一行都是一个计算机指令,前面是一个指令的简写,后面是指令所操做的数据。这个小小的程序首先会将数值 1 “移动”到寄存器 R0,而后把 2 移动到寄存器 R1。03 行把 R0 和 R1 两个寄存器里的数值相加,而后将结果储存在 R2 寄存器里。最后,04 行和 05 行决定结果应该被放在主存储器里的什么位置(在这里是地址 64)。管理内存中存储数据的位置是编程过程当中最耗时也最容易出错的部分之一。编程语言

编译器

汇编器已经比手写计算机指令要好太多了,不过早期的程序员仍是渴望可以按照他们所习惯的方式,像书写数学公式同样地去写程序。这种需求推进了高级编译语言的发展,其中有一些已经成为历史,另外一些现在还在使用。好比 ALGO 就已经成为历史了,可是像 FortranC 这样的语言仍然在不断解决实际问题。

Genealogy tree of ALGO and Fortran
Genealogy tree of ALGO and Fortran

ALGO 和 Fortran 编程语言的谱系树

这些“高级”语言使得程序员能够用更简单的方式编写程序。在 C 语言中,咱们的加法程序就变成了这样:

int x;
x = 1 + 2;
复制代码

第一个语句描述了该程序将要使用的一块内存。在这个例子中,这块内存应该占一个整数的大小,名字是 x。第二个语句是加法,虽然是倒着写的。一个 C 语言的程序员会说这是 “X 被赋值为 1 加 2 的结果”。须要注意的是,程序员并不须要决定在内存的什么位置储存 x,这个任务交给编译器了。

这种被称为“编译器”的新程序能够把用高级语言写的程序转换成汇编语言,再使用汇编器把汇编语言转换成机器可读的程序。这种程序组合经常被称为“工具链”,由于一个程序的输出就直接成为另外一个程序的输入。

编译语言相比汇编语言的优点体如今从一台计算机迁移到不一样型号或者品牌的另外一台计算机上的时候。在计算机的早期岁月里,包括 IBM、DEC、德州仪器、UNIVAC 以及惠普在内的不少公司都在制造除了大量不一样类型的计算机硬件。这些计算机除了都须要链接电源以外就没有太多共同点了。它们在内存和 CPU 架构上的差别至关大,当时常常须要人们花费数年来将一台计算机的程序翻译成另外一台计算机的程序。

有了高级语言,咱们只须要把编译器工具链迁移到新的平台就好了。只要有可用的编译器,高级语言写的程序最多只须要通过小幅修改就能够在新的计算机上被从新编译。高级语言的编译是一个真正的革命性成果。

IBM PC XT
IBM PC XT

1983 发布的 IBM PC XT 是硬件价格降低的早期例子。

程序员们的生活获得了很好的改善。相比之下,经过高级语言表达他们想要解决的问题让事情变得轻松不少。因为半导体技术的进步以及集成芯片的发明,计算机硬件的价格急剧降低。计算机的速度愈来愈快,能力也愈来愈强,而且还便宜了不少。从某个时间点日后(也许是 80 年代末期吧),事情发生了反转,程序员变得比他们所使用的硬件更值钱了。

解释器

随着时间的推移,一种新的编程方式兴起了。一种被称为“解释器”的特殊程序能够直接读取一个程序将其转换成计算机指令以当即执行。和编译器差很少,解释器读取程序并将它转换成一个中间形态。但和编译器不一样的是,解释器直接执行程序的这个中间形态。解释型语言在每一次执行的时候都要经历这个过程;而编译程序只须要编译一次,以后计算机每次只须要执行编译好的机器指令就能够了。

顺便说一句,这个特性就是致使人们感受解释型程序运行得比较慢的缘由。不过现代计算机的性能出奇地强大,以致于大多数人没法区分编译型程序和解释型程序。

解释型程序(有时也被成为“脚本”)甚至更容易被移植到不一样的硬件平台上。由于脚本并不包含任何机器特有的指令,同一个版本的程序能够不通过任何修改就直接在不少不一样的计算机上运行。不过固然了,解释器必须得先移植到新的机器上才行。

一个很流行的解释型语言是 perl。用 perl 完整地表达咱们的加法问题会是这样的:

$x = 1 + 2
复制代码

虽然这个程序看起来和 C 语言的版本差很少,运行上也没有太大区别,但却缺乏了初始化变量的语句。其实还有一些其它的区别(超出这篇文章的范围了),但你应该已经注意到,咱们写计算机程序的方式已经和数学家用纸笔手写数学表达式很是接近了。

虚拟机

最新潮的编程方式要数虚拟机(常常简称 VM)了。虚拟机分为两大类:系统虚拟机和进程虚拟机。这两种虚拟机都提供一种对“真实的”计算硬件的不一样级别的抽象,不过它们的做用域不一样。系统虚拟机是一个提供物理硬件的替代品的软件,而进程虚拟机则被设计用来以一种“系统独立”的方式执行程序。因此在这个例子里,进程虚拟机(日后我所说的虚拟机都是指这个类型)的做用域和解释器的比较相似,由于也是先将程序编译成一个中间形态,而后虚拟机再执行这个中间形态。

虚拟机和解释器的主要区别在于,虚拟机创造了一个虚拟的 CPU,以及一套虚拟的指令集。有了这层抽象,咱们就能够编写前端工具来把不一样语言的程序编译成虚拟机能够接受的程序了。也许最流行也最知名的虚拟机就是 Java 虚拟机(JVM)了。JVM 最初在 1990 年代只支持 Java 语言,可是现在却能够运行 许多 流行的编程语言,包括 Scala、Jython、JRuby、Clojure,以及 Kotlin 等等。还有其它一些不太常见的例子,在这里就不说了。我也是最近才知道,我最喜欢的语言 Python 并非一个解释型语言,而是一个 运行在虚拟机上的语言

虚拟机仍然在延续这样一个历史趋势:让程序员在使用特定领域的编程语言解决问题的时候,所须要的对特定计算平台的了解变得愈来愈少了。

就是这样了

但愿你喜欢这篇简单介绍软件背后运行原理的短文。有什么其它话题是你想让我接下来讨论的吗?在评论里告诉我吧。


via: opensource.com/article/19/…

做者:Erik O'Shaughnessy 选题:lujun9972 译者:chen-ni 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

相关文章
相关标签/搜索