为了显示标号number 所代表的汇编地址,源程序第26 行用于将它的数值传送到寄存器AX,这个和以前是一样的。
声明标号number 并从此处开始初始化5 字节的目的主要是保存数位,但同时我们还想显示它的汇编地址。为了访问标号number 处的数位,需要获取它在内存段中的偏移地址。
为此,源程序第29 行,通过将AX 的内容传送到BX,来使BX 指向该处的偏移地址。实际上,这等效于
只不过用寄存器传递来得更快,更方便。
第29~37 行依旧做的是分解数位的事,但用了和以往不同的方法。简单地说,就是循环。循环依靠的是循环指令loop,该指令出现在源程序的第37 行:
loop 指令的功能是重复执行一段相同的代码,处理器在执行它的时候会顺序做两件事:
和源程序第6 行的jmp near start 一样,loop digit 指令也是颇具迷惑性的指令,它的机器指令操作码是0xE2,后面跟着一个字节的操作数,而且也是相对于标号处的偏移量,是在编译阶段,编译器用标号digit 所在位置的汇编地址减去loop 指令的汇编地址,再减去loop 指令的长度(2)来得到的。
为了使loop 指令能正常工作,需要一些准备。源程序第30 行,将循环次数传送到CX 寄存器。因为分解AX 中的数需要循环5 次,故传送的值是5。
源程序第31 行,将除数10 传送到寄存器SI。
源程序第33~37 行是循环体,每次循环都会执行这些代码,主要是做除法并保存每次得到的余数。每次除法之前都要先将DX 清零以得到被除数的高16 位,这是源程序第33 行所做的事情。
做完除法之后,第35 行,将DL 中得到的余数传送到由BX 所指示的内存单元中去。这是我们第一次接触到偏移地址来自于寄存器的情况,而在此之前,我们仅仅是使用类似于下面的指令:
尽管方式不同,但mov [bx],dl 做相同的事情,那就是把DL 中的内容,传送到以DS 的内容为段地址,以BX 的内容为偏移地址的内存单元中去。注意,指令中的中括号是必需的,否则就是传送到BX 中,而不是BX 的内容所指示的内存单元了。
在8086 处理器上,如果要用寄存器来提供偏移地址,只能使用BX、SI、DI、BP,不能使用其他寄存器。所以,以下指令都是非法的:
原因很简单,寄存器BX 最初的功能之一就是用来提供数据访问的基地址,所以又叫基址寄存器(Base Address Register)。之所以不能用SP、IP、AX、CX、DX,这是一种硬性规定,说不上有什么特别的理由。而且,在设计8086 处理器时,每个寄存器都有自己的特殊用途,比如AX是累加器(Accumulator),与它有关的指令还会做指令长度上的优化(较短);CX 是计数器(Counter);DX 是数据(Data)寄存器,除了作为通用寄存器使用外,还专门用于和外设之间进行数据传送;SI 是源索引寄存器(Source Index);DI 是目标索引寄存器(Destination Index),用于数据传送操作,我们已经在movsb 和movsw 指令的用法中领略过了。
注意,可以在任何带有内存操作数的指令中使用BX、SI 或者DI 提供偏移地址。
做完一次除法,并保存了数位之后,源程序第36 行,用于将BX 中的内容加一,以指向下一个内存单元。inc 是加一指令,操作数可以是8 位或者16 位的寄存器,也可以是字节或者字内存单元。从功能上讲,它和
是一样的,但前者的机器码更短,速度更快。下面是两个例子:
以上,第一条指令执行时,处理器将寄存器AL 中的内容加一;第二条指令执行时,将寄存器BX 所指向的内存单元的内容加一。就是说,处理器用段寄存器DS 的内容左移4 位,加上寄存器BX 的内容,形成20 位物理地址。然后,将该地址处的内容(字节)加一。
第三条指令做和第二条指令相同的事情,但是偏移地址是用标号给出的。关键字“word”表明它操作的是内存中的一个字,段地址在段寄存器DS 中,偏移地址等于标号label_a 在编译阶段的汇编地址。
和inc 指令相对的是dec 指令,用于将目标操作数的内容减一,它们的指令格式相同,不再赘述。
源程序第37 行,正是loop 指令。就像我们刚才说的,它将CX 的内容减一,并判断是否为零。如果不为零,则跳转到标号digit 所在的位置处执行。
很显然,在指令的地址部分使用寄存器,而不是数值或者标号(其实标号是数值的等价形式,在编译后也是数值)有一个明显的好处,那就是可以在循环体里方便地改变偏移地址,如果使用数值就不能做到这一点。
检测点6.2
选择题:下面哪些指令是错误的,为什么?
A.add ax,[bx] B.mov ax,[si] C.mov ax,[cx] D.mov dx,[di]
E.mov dx,[ax] F.inc byte [di] G.div word [bx]