第1章 绪论
早期的电子计算机都是巨大的装置,需要占据好几个房间,耗电量相当于一个大型工厂,造价数百万美元(但计算能力甚至比现代最简单的手机都要低)。使用这些机器的程序员认为计算机的时间比他们的时间更有价值,所以他们用机器语言进行编程。机器语言是直接控制处理器的位序列,使其在适当的时候进行加法和比较运算,并将数据从一个地方移动到另一个地方,等等。在这种级别上进行程序设计是一项极其乏味的任务。(EXAMPLE 1.1 GCD program in x86 machine language)下面的程序使用欧几里德算法计算两个整数的最大公因数,它是用机器语言编写的,这里表示为十六进制(以16为基数),用于x86指令集。
55 89 e5 53 83 ec 04 83 e4 f0 e8 31 00 00 00 89 c3 e8 2a 00
00 00 39 c3 74 10 8d b6 00 00 00 00 39 c3 7e 13 29 c3 39 c3
75 f6 89 1c 24 e8 6e 00 00 00 8b 5d fc c9 c3 29 d8 eb eb 90
随着人们开始编写大型程序,他们意识到需要一种不易出错的程序表示方式。汇编语言是一种通过助记符的方式表达操作的语言。(EXAMPLE 1.2 GCD program in x86 assembler)上面的最大公因数程序的x86汇编表示方式为:
pushl %ebp
movl %esp, %ebp
pushl %ebx
subl $4, %esp
andl $-16, %esp
call getint
movl %eax, %ebx
call getint
cmpl %eax, %ebx
je C
A: cmpl %eax, %ebx
jle D
subl %eax, %ebx
B: cmpl %eax, %ebx
jne A
C: movl %ebx, (%esp)
call putint
movl -4(%ebp), %ebx
leave
ret
D: subl %ebx, %eax
jmp B
最初设计汇编语言时,助记符和机器语言指令之间是一一对应的关系,如EXAMPLE 1.2所示1。将助记符翻为机器语言的系统程序称为汇编器。后来,汇编器开始支持“宏展开”功能,这使得程序员可以为常用的指令序列定义参数化形式,但是汇编语言和机器语言之间的对应关系仍然是明确的。程序设计仍然是一个以面向机器的工作:不同类型的计算机使用其所支持的汇编语言编程,程序员按照机器实际执行的指令的方式进行思考。
随着计算机技术的发展,人们意识到为每台新机器重新编写程序是一件令人沮丧的事情,而且跟踪大型汇编程序的细节也变得越来越困难。人们开始寻求一种独立于机器的语言,特别是该语言可以用更接近数学公式的形式来表达数值计算(这是当时最常见的程序类型)。这种诉求使得人们在20世纪50年代中期发明了Fortran,这是第一个可以称得上是高级编程语言的原始方言。此后其它高级语言也很快跟进,尤其是Lisp和Algol。
将高级语言转换为汇编语言或机器语言是一个称为编译器的系统程序的工作2。编译器要比汇编器复杂得多,因为当源代码是高级语言时,源代码和目标代码之间不再存在一对一的对应关系。Fortran一开始普及的很慢,因为程序员几乎总能编写出比编译器运行速度更快的汇编语言程序。然而随着时间的推移,这种差距慢慢缩小,并最终反过来了。硬件复杂性的增加(例如流水线和多功能单元等)和编译器技术的不断改进导致了一种情况,即最先进的编译器通常会生成比程序员编写的更好的代码。即使在程序员可以做得更好的情况下,计算机速度的提升和程序规模的增大也使得降低程序员的成本变得越来越重要——不仅是在最初的程序编写中,而且在随后的程序维护、增强和修复中。劳动力成本现在大大超过了计算硬件的成本。
1. 本例中的22行汇编代码在机器语言中是变长编码的。例如三条cmp指令有着相同的寄存器操作数,所以编码后的结果为两字节(39 c3)。四条mov指令有着不同的操作数,因此以89或8b开头。上面的汇编语言是GNU gcc编译器语法,结果写入最后一个操作数,而不是第一个操作数。 ↩
2. 高级语言也可以无需翻译直接解释执行。我们将在1.4节中讨论此内容。这是Python和JavaScript等脚本语言的主要实现方式。 ↩