在计算机的早期,硬件昂贵,程序员便宜。 事实上,程序员太便宜了,甚至不被称为“程序员”,他们实际上通常是数学家或电气工程师。 早期的计算机用于快速解决复杂的数学问题,因此数学家自然适合“编程”这项工作。
什么是程序?
首先,做一点背景介绍。 计算机本身什么也做不了,因此它们需要程序来驱动其行为。 程序可以被认为是详细的配方,它接受一个输入并产生一个输出。 配方中的步骤由操作数据的指令组成。 虽然这听起来很复杂,但您可能知道这条语句是如何工作的
1 + 2 = 3
加号是“指令”,而数字 1 和 2 是数据。 从数学上讲,等号表示等式两边是“等价的”,但是大多数计算机语言使用等号的某种变体来表示“赋值”。 如果计算机正在执行该语句,它会将加法的结果(“3”)存储在内存中的某个位置。
计算机知道如何用数字做数学运算,以及在机器的内存层次结构中移动数据。 我不会过多地谈论内存,只是它通常有两种不同的形式:快速/小 和 慢速/大。 CPU 寄存器非常快、非常小,并且充当草稿纸。 主内存通常非常大,但速度不如寄存器内存。 在程序执行时,CPU 会将它们正在处理的数据从主内存移动到寄存器,然后再移回来。
汇编器
计算机非常昂贵,而人工很便宜。 程序员花费大量时间将手写的数学运算转换为计算机可以执行的计算机指令。 最早的计算机具有糟糕的用户界面,有些仅由前面板上的拨动开关组成。 这些开关表示内存中单个“字”中的 1 和 0。 程序员会配置一个字,指示将其存储在哪里,并将该字提交到内存中。 这既费时又容易出错。

程序员 Betty Jean Jennings(左)和 Fran Bilas(右)在 摩尔电气工程学院 操作 ENIAC 的主控制面板。 (美国陆军照片,来自 ARL 技术图书馆的档案)
最终,一位 电气工程师 认为他的时间并不便宜,因此编写了一个程序,其输入以人们可以阅读的“配方”形式表达,并输出计算机可读的版本。 这是第一个“汇编器”,它引起了很大的争议。 那些拥有昂贵机器的人不希望在人们已经在做的事情上“浪费”计算时间; 尽管速度慢且容易出错。 随着时间的推移,人们开始欣赏汇编器相对于手工汇编程序的 speed 和准确性,并且使用计算机完成的“实际工作”量增加了。
虽然汇编程序比将位模式拨动到机器的前面板上迈出了一大步,但它们仍然非常专业。 上面的加法示例可能看起来像这样
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 就是这样一个脚注,而诸如 Fortran 和 C 等语言今天仍在用于解决实际问题。

编程语言的族谱,Algol 和 Fortran 家族
这些“高级”语言的引入允许程序员用更简单的术语编写他们的程序。 在 C 语言中,我们的加法汇编程序将编写为
int x;
x = 1 + 2;
第一条语句描述了程序将使用的一块内存。 在本例中,内存的大小应为整数大小,其名称为 x 第二条语句是加法,尽管是“向后”编写的。 C 程序员会将其解读为“X 被赋值为 1 加 2 的结果”。 请注意,程序员不需要说明将 x 放在内存中的哪个位置,因为编译器会处理它。
一种新的程序类型,称为“编译器”,会将用高级语言编写的程序转换为汇编语言版本,然后通过汇编器运行它,以生成程序的机器可读版本。 这种程序的组成通常称为“工具链”,即一个程序的输出直接发送到另一个程序的输入。
编译语言相对于汇编语言程序的最大优势是将程序从一个计算机型号或品牌移植到另一个型号或品牌。 在计算机的早期,来自 IBM、Digital Equipment Corporation、Texas Instruments、UNIVAC、Hewlett Packard 等公司的不同类型的计算硬件大量涌现。 除了需要插入电源外,这些计算机几乎没有共同之处。 内存和 CPU 架构差异很大,通常需要数年才能将程序从一台计算机翻译到另一台计算机。
使用高级语言,只需要将编译器工具链移植到新平台即可。 一旦编译器可用,就可以在很少或没有修改的情况下为新计算机重新编译高级语言程序。 高级语言的编译确实是革命性的。

1983 年发布的 IIBM PC XT
程序员的生活变得非常美好。 使用高级语言表达他们想要解决的问题变得容易得多。 由于半导体技术的进步和集成芯片的发明,计算机硬件的成本急剧下降。 计算机变得更快、更强大,而且价格也便宜得多。 在某个时候,可能是在 80 年代后期,出现了一种反转,程序员变得比他们使用的硬件更昂贵。
解释器
随着时间的推移,一种新的编程模型兴起,其中一种名为“解释器”的特殊程序会读取一个程序,并将其转换为要立即执行的计算机指令。 解释器将程序作为输入,并将其解释为一种中间形式,很像编译器。 与编译器不同,解释器然后执行程序的中间形式。 这发生在每次运行解释程序时,而编译程序只编译一次,计算机“按原样”执行机器指令。
顺便说一下,当人们说“解释程序很慢”时,这是感知到的性能不足的主要原因。 现代计算机的功能非常强大,以至于大多数人无法区分编译程序和解释程序。
解释程序,有时称为“脚本”,更容易移植到不同的硬件平台。 因为脚本不包含任何特定于机器的指令,所以单个版本的程序可以在许多不同的计算机上运行,而无需进行更改。 当然,关键是必须将解释器移植到新机器才能实现这一点。
一种非常流行的解释语言的例子是 perl。 我们的加法问题的完整 perl 表达式是
$x = 1 + 2
虽然它看起来和 C 版本非常相似,但它缺少变量初始化语句。 还有其他差异(这超出了本文的范围),但您可以看到我们可以编写一个计算机程序,它非常接近数学家用铅笔和纸手工编写程序的方式。
虚拟机
编程模型领域的最新潮流是虚拟机,通常缩写为 VM。虚拟机有两种类型:系统虚拟机和进程虚拟机。这两种类型的虚拟机都提供了与“真实”计算硬件的一定程度的抽象,尽管它们的范围不同。系统虚拟机是一种提供物理硬件替代品的软件,而进程虚拟机旨在以系统独立的方式执行程序。因此,在这种情况下,进程虚拟机(以下简称虚拟机)在范围上类似于解释器,因为程序首先被编译成中间形式,然后虚拟机执行它。
解释器和虚拟机之间的主要区别在于,虚拟机实现了一个理想化的 CPU,通过其虚拟指令集进行访问。这种抽象使得编写前端语言工具成为可能,这些工具可以编译用不同语言编写的程序,并以虚拟机为目标。可能最流行和最著名的虚拟机是 Java 虚拟机 (JVM)。JVM 最初仅适用于 20 世纪 90 年代的 Java 编程语言,但现在它托管着许多流行的计算机语言:Scala、Jython、JRuby、Clojure 和 Kotlin 等等。还有其他可能不为人所知的例子。我最近才知道,我最喜欢的语言 Python 不是一种解释型语言,而是一种托管在虚拟机上的语言!
虚拟机延续了历史趋势,减少了程序员表达其特定领域需求的语言时,所需的特定于平台的知识量。
总结一下
我希望您喜欢这篇关于软件中一些不太可见部分的入门文章。您希望我接下来深入研究哪些其他主题?请在评论中告诉我。
本文最初发表于 PyBites 并经许可转载。
4 条评论