汇编语言编程基础:基于LoongArch
上QQ阅读APP看书,第一时间看更新

1.1 计算机语言

前文所罗列的语言统称为计算机语言。计算机语言就是用于人和计算机之间交流的语言。计算机是一组电子器件,要让它完成特定的工作,就需要向它输入一组它能识别和执行的语言(或者叫指令)。和人类语言一样,计算机语言也有一套标准的语法规范,有了规范才得以让计算机理解我们的意图并遵照执行。

计算机语言的种类很多,从使用层次的角度常被分成机器语言、汇编语言和高级语言三大类。简单来说,离计算机处理器最远(需要更多的编译流程后才能被计算机识别和执行)、层次最高(更接近自然语言和数学公式)的是高级语言,例如C、C++、Java等都属于高级语言;中间层次的是汇编语言;离计算机最近、处于最低层次的是机器语言,机器语言是唯一可以被计算机直接识别和执行的语言。绝大多数的计算机软件开发人员(或称程序员)通常使用更接近人类语言语法规则、更易于编写的高级语言来编写程序,然后利用编译器、汇编器把高级语言一步步转换成计算机可以识别的机器语言,最终交由计算机处理器执行并得出结果。这个流程如图1-1所示。

图1-1 计算机语言转换过程

图1-1简单描绘了高级语言到机器语言的转换过程,涉及的工具是编译器和汇编器。其中编译器负责把高级语言(比如C++语言)翻译成汇编语言,汇编器又把汇编语言翻译成机器语言。而机器语言就是最终可以被计算机识别和执行的语言。

下面按照与计算机处理器的距离,以由近及远的顺序分别介绍机器语言、汇编语言和高级语言。

1.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 龙芯指令集中加法运算指令的语法规范

从图1-2可以看出,在龙芯指令集中,一条指令的长度为32位。对于加法指令,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,即代表第3个寄存器;最后的5位(0 0011)为目的操作数,看来也是第3个寄存器。故这条机器指令功能是实现第3个寄存器值和常数64的加法运算,将结果存入第3个寄存器。龙芯汇编指令的写法就是addi.d r3, r3, 64。此条机器指令按语法规范表示为

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

要解读程序中每一条机器指令所代表的意思,要不断地对照指令手册来翻译。推想开来,我们要让计算机完成一个功能可能需要成千上万个这样的指令,如果使用机器指令编写,难度可想而知。故有了后来更易读、易编写的汇编语言和高级语言。

1.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指令集的处理器上,反之亦然。

1.1.3 高级语言

高级语言是一个相对概念,通常可解读为越是易于程序员高效编写的语言越高级。例如刚开始出现C语言时,人们认为C语言比汇编语言高级,故称C语言为高级语言,而汇编语言为低级语言,当Java、Python语言出现后,人们又认为C语言不够高级。本书提到的高级语言是相对于汇编语言而言的,即不再强依赖计算机处理器的硬件体系架构、表达方式更接近自然语言和数学公式的程序设计语言,比如C、C++、Java、Go等。这些语言本身都是独立于处理器架构的,都有自己的语法规则且不能直接被计算机识别和执行,需要编译器和汇编器的翻译过程,相关程序被转变为机器指令后才被处理器识别和执行。例如要实现一个数的累加运算,使用C语言的编写如下:

   ++a;  

一条语句就完成了变量a的累加功能,如果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等)等。未来还可能有面向应用的设计思想转变,也就是说:只需要告诉程序你要干什么,程序就能自动生成算法,自动进行处理。高级语言设计思想的不断进化,让计算机语言越来越接近人类语言,也更智能,编程效率也越来越高,使得程序员可把更多时间花费在解决复杂业务场景上。