2.1. 计算机语言

前文提到的各类语言,都属于计算机语言。计算机语言用于在人和计算机之间传递信息。计算机由大量电子器件组成,要让它完成某项任务,必须向它提供能够识别并执行的表达形式,也就是指令。和人类语言一样,计算机语言也有固定的语法规则;计算机依据这些规则解析用户意图,并执行相应操作。

计算机语言有多种类型。按抽象层次划分,通常包括机器语言、汇编语言和高级语言。高级语言离处理器最远,抽象程度最高,写法更接近自然语言和数学表达式,例如C、C++、Java等;它一般需要经过编译或转换,才能被计算机执行。汇编语言处于两者之间。机器语言最接近硬件,抽象层次最低,也是计算机唯一可以直接识别和执行的语言。多数软件开发人员通常使用高级语言编写程序,再由编译器和汇编器逐步转换为机器语言,最终交给处理器运行并产生结果。这个过程如图1-1所示。

计算机语言转换过程

图1-1展示了高级语言转换为机器语言的基本流程,核心工具包括编译器和汇编器。编译器把高级语言程序(如C++程序)翻译为汇编语言,汇编器再把汇编语言转换成机器语言。机器语言是处理器最终能够理解并执行的形式。

下面按照与处理器的距离,从近到远依次介绍机器语言、汇编语言和高级语言。

2.1.1. 机器语言

机器语言是计算机可以直接执行的程序语言,通常以二进制形式表示。进制是一种计数规则。人们熟悉的十进制使用0~9这10个数字,并遵循“逢十进一”;二进制只使用“0”和“1”,规则是“逢二进一”。计算机硬件由电路构成,最容易用有电和无电两种状态表示信息,这两种状态也称为高电平和低电平,分别对应二进制中的“1”和“0”。一个二进制数字称为一位(bit)。

机器语言由多条机器指令组成,机器指令通常简称为指令。一条指令由固定长度的二进制数表示,用来要求计算机完成某个动作,例如加法、减法、逻辑与运算,或者从内存读取数据。因此,指令是处理器执行操作的基本单位。处理器提供给程序员使用的全部指令集合,称为指令集或指令系统。指令系统可以看作软件和硬件之间的接口,程序员通过它指挥处理器完成工作。常见指令系统包括x86、ARM等。中央处理器(Central Processing Unit,CPU)是计算机的核心部件,负责解释指令并处理软件数据。某种CPU只能识别与自身指令集匹配的指令,例如x86指令只能由采用x86指令集的处理器识别,不能直接运行在ARM或MIPS处理器上。在本书中,指令集和体系架构的含义较为接近,通常都涉及一组指令以及寄存器等相关资源。

龙芯指令系统中,每条指令占32位。若要让龙芯处理器执行一次加法操作,对应的机器指令可能如下:

0000 0010 1100 0001 0000 0000 0110 0011

这串数字并不便于阅读,人们很难直接从32个0和1中看出它的含义。不过,机器指令并不是随意排列的。和自然语言、高级语言一样,机器指令也遵循特定语法。一条固定长度的机器指令(如龙芯指令为32位)通常由操作码和操作数组成;操作数又可分为源操作数和目的操作数。下面用C语言语句进行类比。

long c = a + 2;

在这条语句中,符号“+”可类比为操作码,表示执行加法。变量a和常数2是源操作数,变量c是目的操作数,用来保存计算结果。机器指令的结构与此类似,但机器指令中的操作码不仅要说明运算类型(如加、减、乘等),还要说明参与运算的数据类型(如int、double、long等)。机器指令中的操作数通常是寄存器(计算机中临时保存数据的器件)或常数。以龙芯指令集中的加法指令为例,其语法格式如图1-2所示。

龙芯指令集中加法运算指令的语法规范

由图1-2可知,龙芯指令集中的一条指令长度为32位。对于加法指令,高10位表示操作码。0b0000001010表示32位加法指令(ADDI.W),0b0000001011表示64位加法指令(ADDI.D)。之后的12位表示一个常数;再后面的两个5位字段分别表示源寄存器操作数ri和目的寄存器操作数rd。ri和rd可以取龙芯指令集提供的32个通用寄存器中的任意一个。

结合图1-2,可以解析前述机器指令的含义。高10位(0000 0010 11)是操作码,表示带立即数的64位整数加法,对应C语言中的long类型;随后的12位(00 0001 0000 00)是第一个源操作数,它是一个常数,换算成十进制为64;再后5位(00 011)是第二个源操作数,表示寄存器编号3;最后5位(0 0011)是目的操作数,同样表示第3个寄存器。因此,这条指令的作用是把第3个寄存器中的值与常数64相加,并把结果写回第3个寄存器。它对应的龙芯汇编写法是addi.d r3, r3, 64。按字段划分,该机器指令可表示为:

0000 0010 11 | 00 0001 0000 00 | 00 011 	| 0 0011
操作码		第一个源操作数	 第二个源操作数    目的操作数

如果要逐条理解程序中的机器指令,通常需要反复查阅指令手册。更进一步,一个实际功能往往需要成千上万条类似指令共同完成。直接用机器指令编程显然非常困难,因此人们引入了更便于阅读和编写的汇编语言,以及抽象层次更高的高级语言。

2.1.2. 汇编语言

汇编语言可以理解为机器语言的符号化形式。它使用较容易记忆的字母、单词和符号来表示特定机器指令,使程序更容易阅读,也更容易判断其功能。例如,在1.1.1小节中,龙芯架构下实现两个数相加,对应的机器指令和汇编指令如下:

机器指令: 0000 0010 1100 0001 0000 0000 0110 0011
汇编指令: addi.d r3,r3,64

从汇编形式可以更直接地看出,这条指令完成寄存器r3与常数64的加法,并把结果写回r3。这样就不必逐位对照指令手册解释操作码和操作数。一条汇编指令一般由助记符和操作数组成。助记符对应机器指令中的操作码,例如addi.d表示64位加法;操作数表示参与运算的数据对象,例如这里的两个r3和常数64。

从这个例子可以看出,使用汇编语言时,程序员无需直接处理指令的二进制编码。汇编器会把汇编指令翻译为机器语言,从而明显降低编写难度并提升效率。

汇编语言和机器语言一样,都与具体计算机体系架构密切相关。也就是说,使用龙芯汇编指令集编写的程序,未经转换不能直接运行在x86或ARM处理器上;反过来也一样。

2.1.3. 高级语言

高级语言是相对概念。一般来说,越有利于程序员高效表达程序逻辑的语言,抽象层次就越高。C语言刚出现时,人们认为它比汇编语言更高级,因此称其为高级语言,而把汇编语言归为低级语言;Java、Python等语言出现后,C语言又显得抽象层次较低。本书中的高级语言,是相对于汇编语言而言的,指不再强依赖处理器硬件架构、表达方式更接近自然语言和数学公式的程序设计语言,例如C、C++、Java、Go等。这类语言有各自的语法规则,通常与具体处理器架构无关,但不能被处理器直接执行,必须经过编译器和汇编器翻译成机器指令。例如,要实现变量自增,C语言可以写成:

++a;

这一条语句即可完成变量a加1。如果a的初始值为1,执行++a后,a的值变为2。对应的汇编指令通常至少需要3条:先从内存地址读取a的初始值到寄存器,再让该寄存器与常数1相加,最后把结果写回内存。该过程可用下面的龙芯汇编指令表示:

load	r3,[addr]	//	从内存地址addr加载值到寄存器r3
add 	r4,r3,1 	//	加法计算r3+1,将结果写到寄存器r4
store 	r4,[addr]	//	把寄存器r4的值写回到内存地址addr

对比可以发现,C语言写法更直观,也更适合程序员完成日常开发。并且,同一份高级语言程序通常只需编写一次,面向不同体系架构的机器语言可以由相应编译器生成。

高级语言发展的核心目标,是让程序员更快、更高效地完成编程。围绕这一目标,编程思想经历了多个阶段:面向过程把功能块组织为函数或方法,典型代表是C语言;面向对象把相关数据和方法封装为整体进行管理,典型代表是Java;函数式编程强调高阶函数等特性,Java、Groovy、Scala、JavaScript等语言都提供了相关支持。未来的语言设计可能进一步转向应用意图表达,即程序员只描述任务目标,系统自动生成算法并完成处理。随着高级语言持续演进,计算机语言越来越接近人类表达方式,智能化程度和编程效率也不断提升,程序员因此可以把更多精力放在复杂业务问题的解决上。