编程究竟是怎么回事(二)——汇编语言

2019-02-28

注:本文写于2017年12月,首先发表于微信公众号“程艺的编程之路”上

高级语言我想大家应该都了解了。基本上和数学相关的专业都会学一门高级语言,不管是不是计算机相关专业。相信我的读者们绝大多数都是学过高级语言的。

那么接下来我们要讲的是汇编语言,它比高级语言更接近计算机的底层。高级语言为什么叫做高级语言就是因为它下面还有汇编语言和机器语言(下一期讲到)这两层较为“低级”的语言。

不同的处理器架构

要说到汇编语言,先要说到不同的计算机架构。不同用途的计算机处理器会采用不同种类的架构,就比如智能手机和个人电脑,大家一定都能想到它们采用的处理器不是一个体系的,这个体系在计算机领域的说法就叫架构。具体来说,个人电脑的处理器使用的是x86架构,智能手机处理器使用的是arm架构。不同处理器架构的计算机所用到的汇编语言也不同,这是因为不同架构的处理器的指令集不同,指令集指的就是处理器能够执行的指令的集合。

CISC和RISC

在指令集这方面有两种大类,一种叫做复杂指令集(CISC),一种叫做简单指令集(RISC)。顾名思义,复杂指令集中的指令数量更多更复杂(有上百条),简单指令集中的指令更少更简单(有几十条)。CISC可以在一条指令中执行一个复杂的操作,而RISC执行一个同样复杂的操作需要多条简单指令的组合来完成。看起来好像CISC在处理复杂的任务时更好,其实也难说,因为RISC的每一条简单指令执行的速度都很快,而CISC执行一条复杂指令的时间较长,所以在组合起来之后真说不准谁更快。而且CISC带来的最大问题就是指令集难以维护,过多的指令造成指令集臃肿不堪。所以现在更为推崇RISC的设计理念。

x86架构和arm架构对比

我们的个人电脑使用的x86架构属于复杂指令集(CISC)。前文不是说更推崇RISC吗,为什么英特尔却采用CISC呢?这是因为英特尔一开始研发处理器的时候搞RISC的那些科学家还没有研究出成果呢。等到RISC发表之后再想推翻之前的设计已经来不及了。英特尔也尝试过做RISC处理器,但由于和之前的软件不兼容,所以市场反响不好。而之后英特尔在CISC处理器中也融入了一些RISC的思想,同时又保持了兼容性,可谓是两全其美的做法。

我们的智能手机使用的arm架构属于精简指令集(RISC)。这也是目前推崇的设计理念。关于这个我就不多说了,我们进入汇编语言的正题。汇编语言比高级语言低一层,它的执行效率更快,更能符合硬件工作的原理,但相比于高级语言来难以理解而且难以维护。我们平常写的高级语言语句更符合人类的思维,真正在计算机上执行时都得首先转成汇编语言语句才行。


再来说一下,我了解过两种不同架构的汇编语言,一种是x86的,一种是MIPS的(另外一种RISC架构,当前中国自主研发的处理器龙芯采用的就是这种架构)。所以我会把这两种汇编语言做比较。


寄存器

突然想到还有一个概念没讲到,这就是寄存器。寄存器是CPU内部的一个部件,用来存数,访问速度远远高于我们之前说到过的内存、硬盘。在x86架构中最常用到的是通用寄存器,主要用于算术运算和数据传输。X86有八个通用寄存器,它们的名字分别为:EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP。MIPS的寄存器略有不同,它总共有32个寄存器,不同寄存器的用途分得更加细致,其中保存变量的寄存器名字从\$s0~\$s7,还有一个寄存器的名字叫$0,它永远存的是0这个常数。

具体讲解汇编语言

现在就可以正式开始讲汇编了。把数字5存到一个寄存器中,x86的汇编语句是这样的:

mov eax,5

mov代表移动(move),可以看出这条语句的意思就是把5移动到eax寄存器中。MIPS是这样的

addi $s0,$0,5

这条语句的意思是\$s0=$0+5.MIPS没有mov指令,所以需要用两个常数加起来的方法对寄存器进行赋值。下面再来说说加法语句,把一个寄存器的值加上另一个寄存器的值再保存回原寄存器中,x86是这样写的:

add eax,ebx

写成C语言的话就相当于

eax=eax+ebx

或者更简洁些就是

eax+=ebx

可以看到x86的加法汇编语句只有两个操作数。MIPS的加法语句就不一样了,它得这样写

add $s0,$s0,$s1

有三个操作数,更清晰的显示出\$s0=\$s0+\$s1。前面的addi和add很像,它的作用是运算有常数参与的加法,而add只能运输操作数都是寄存器的加法。从这里看不太出CISC和RISC的区别,那么下面的例子就很明显了。即C语言中的if语句,x86有很多很多种跳转,比如je(相等跳转),jne(不相等跳转),ja(大于跳转),jae(大于等于跳转),jb(小于跳转),jbe(小于等于跳转),jna(不大于跳转),jnb(不小于跳转)……MIPS就很简单了,只有bne(不等于跳转)和beq(等于跳转),同时它提供了slt用于在比较两个数大小时设置目的操作数(相当于比较的结果)为0或1,然后再把目的操作数去和$0(永远存0)用bne或者beq去比较,从而判断是否跳转。具体的代码例子如下:
C语言:

if(a<b)
 a=0;

x86:


cmp a,b;比较a和b的大小
jae out;如果a大于等于b,则跳到out标志位,即不执行mov a,0
mov a,0;a=0
out: ;out标志位

MIPS:


slt $t1,a,b #如果a小于b,则把$t1寄存器的值设为1,否则设为0
beq $t1,$0,done #把$t1的值和0比较,如果相等(代表a大于等于b),则跳到done标签,不执行add a.$0,$0
add a,$0,$0 #a=0+0
done: #done标签

汇编语言就介绍到这里,如果大家有兴趣的话,可以自己继续去探究,下一期我们将讲述计算机程序语言的底层——机器语言