ITEEDU

前面章节中提到过一些有关累加器AX的几个专门的用途,比如CPU与端口交换数据的专门通道;与"串指令"相配合时用作数据传输等等。这里又要学到累加器的一个特殊用途:使用乘法指令时累加器用于保存一个因数,因此"MUL"指令只有一个操作数。如果将这个操作数据看作是乘数,那么在使用"MUL"指令之前必须先将被乘数存入累加器AX中。

乘法指令有一些重要的性质是需要记住的:

① 相乘的两个数位宽应该相等,8位数据只能与8位数据相乘,16位数据只能与16位数据相乘,而且相乘的两个数都被认为是无符号数;
② 如果进行8位数据的乘法,被乘数必须放入累加器的低8位AL中;
③ 两个8位数相乘结果是一个16位的数据,由AX寄存器保存。两个16位的数相乘结果是一个32位的数据,其高16位由DX寄存器保存,低16位由AX寄存器保存。因此使用MUL指令之前应注意保存AH与DX寄存器中的有用数据;

与乘法指令相对的是除法指令: 助记符:DIV(Divide)
用 途:将两个数相除
格 式:DIV 寄存器
DIV 存储单元
执 行:两个数相除,得出商与余数。

除法指令与乘法指令一样有一些特殊的规定:

① 被除数只能是16位或32位数据,除数只能是8位或16位数据,且16位的被除数只能由8位的数除,而32位的被除数只能由16位的数除。这好象有些不讲理,但是Intel就是这样规定的。
② 32位的被除数要预先放入DX:AX寄存器中,DX寄存器保存高16位;16位的被除数要预先放入AX寄存器中。32位数除以16数据所得的商由AX寄存器保存,余数由DX寄存器保存;16位数据除以8位数据所得的商由AL寄存器保存,余数由AH寄存器保存。
③ 与MUL指令一样,DIV指令同样认为相除的两个数都是无符号数,它也不能处理负数。
④ 除数不能是0。

这第四项似乎是一句废话,其实不然。这一项将引出一个很重要的知识点,这个知识点将在后面的章节中加以介绍。

下面给出了两个试验程序,用于研究乘除法指令的执行过程:

试验<1>
C:\ASM\>DEBUG[Enter]
-a100[Enter]
13C6:0100 mov al,3
13C6:0102 mov ah,4
13C6:0104 mul ah
13C6:0106 mov bx,-1
13C6:0109 mul bx
13C6:010B [Enter]

-g=100 104[Enter]?      注意被乘数、乘数均在AX寄存器中
AX=0403 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=13C6 ES=13C6 SS=13C6 CS=13C6 IP=0104 NV UP EI PL NZ NA PO NC
13C6:0104 F6E4 MUL AH

为了计算3x4的值,我们需首先将3放人AL寄存器中,将4放入另一8Bit寄存器中,然后用指令"MUL 寄存器"计算出结果。这个程序特意用AH寄存器保存乘数,主要是想说明运算结果将是一个出现在AX寄存器里的16位的数据。注意我们没给出"MUL 立即数"的用法,这样使用是错误的。

-tAH寄存器的原值被乘积覆盖
AX=000C BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=13C6 ES=13C6 SS=13C6 CS=13C6 IP=0106 NV UP EI PL NZ NA PE NC
13C6:0106 BBFFFF MOV BX,FFFF

接下来的程序反映了"MUL"指令对有符号数的处理,我们利用"G"命令一次将其执行完,看看程序执行的结果如何:

-g=106 10b[Enter]
积的低16位       乘数积的高16位
AX=000C BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=13C6 ES=13C6 SS=13C6 CS=13C6 IP=010B NV UP EI PL NZ AC PE CY
13C6:010B 90 NOP

以上三行指令跟踪的结果反映了两个16位数据相乘的结果,可以看到MUL指令的确不能处理负数。它把-1(0FFFFH)当作了65535,所以得出的结果是000BFFF4H,其高位000BH出现在DX寄存器中。

为什么不能将16位数据与8位数据相乘?这是因为被乘数究竟是8位还是16位完全取决于"MUL"指令后面的操作数。如果这个操作数是一个8位的寄存器或者是内存中的一个字节,那么CPU就会认为被乘数是AL寄存器中的一个8位数据;反之,如果这个操作数是一个16位的寄存器或者内存中的一个字,那么CPU就会认为被乘数是AX寄存器中的一个16位数据。

试验<2>
C:\ASM\>DEBUG[Enter]
-a100[Enter]
13C6:0100 mov ax,fff6
13C6:0103 mov dx,b
13C6:0106 mov bx,c
13C6:0109 div bx
13C6:010B mov ax,2000
13C6:010E mov dx,ff
13C6:0111 mov bl,ff
13C6:0113 div bl
13C6:0115 [Enter]

-g=100 109[Enter]
被除数的低16位除数?   被除数的高16位
AX=FFF6 BX=000C CX=0000 DX=000B SP=FFEE BP=0000 SI=0000 DI=0000
DS=13C6 ES=13C6 SS=13C6 CS=13C6 IP=0109 NV UP EI PL NZ NA PO NC
13C6:0109 F7F3 DIV BX

以下是"DIV"指令执行后的寄存器情况,从中可以看到商与余数数的位置。000BFFF6H除以000CH应该商0FFFFH余2,从程序执行的结果来看AX寄存器中的确保存着商数而余数在DX寄存器中。

-t?     商?     余数
AX=FFFF BX=000C CX=0000 DX=0002 SP=FFEE BP=0000 SI=0000 DI=0000
DS=13C6 ES=13C6 SS=13C6 CS=13C6 IP=010B NV UP EI PL NZ NA PO NC
13C6:010B B80020 MOV AL,2000

与MUL指令一样,CPU判断被除数是32位还是16位同样依据DIV指令之后的操作数。若操作数是16位,则被除数就是由DX与AX寄存器共同保存的32位数据;若操作数是8位,则被除数就是AX寄存器中的16位数据。请看下面的跟踪结果:

-g=10b 113[Enter]
被除数的低16位除数?        被除数的高16位
AX=2000 BX=00FF CX=0000 DX=00FF SP=FFEE BP=0000 SI=0000 DI=0000
DS=13C6 ES=13C6 SS=13C6 CS=13C6 IP=0113 NV UP EI PL NZ NA PO NC
13C6:0113F6F3DIV BL

我们有意"创造"了一个32位的被除数00FF2000H,然而除数是一个8位二进制数,通过以下的跟踪结果我们可以看到究竟谁是真正的被除数。

-t?商与余数
AX=2020 BX=00FF CX=0000 DX=00FF SP=FFEE BP=0000 SI=0000 DI=0000
DS=13C6 ES=13C6 SS=13C6 CS=13C6 IP=0115 NV UP EI PL NZ AC PE CY
13C6:0115 90 NOP

虽然DX寄存器中存入了00FFH,但由于DIV指令的操作数是8位寄存器BL,所以DX寄存器并未参与这次除法运算,真正的被除数是AX寄存器中的2000H。

试验<3>
C:\ASM\>DEBUG[Enter]
-a[Enter]
13C6:0100 mov ax,2000
13C6:0103 mov bl,2
13C6:0105 div bl
13C6:0107 

上面这个程序是一个根本不能正确运行的程序,原因在于除得的商太大,AL寄存器根本装不下计算结果。这样的现象称为除法溢出,执行这个程序会产生意想不道的结果,可能是死机,也可能是其它什么结果。

花这么多笔墨讲解一条指令似乎有些不值当,但我的最终目的并非只为了讲解这条指令,而是想告诉大家学习汇编指令的一种方法。从某种角度来说试图通过读书甚至背书来掌握这些指令并没有多大效果,要想尽快学会汇编语言最佳的途径就是多多实验。我们有DEBUG这样的好老师,它可以让每条指令都活动起来,并且能告诉我们每条指令执行的结果。通过DEBUG我们可以动态地学习汇编指令,这比死记硬背的效果要好得多。这种研究问题的方法我们在前面已有所表现,希望大家能掌握这种方法并将其应用到自己的学习中去。

上一章的最后曾经留下一个程序未做说明,学过这一节后我想那个程序已经没有详细分析的必要了。不知道大家是否对"直接写屏"这个名词有所耳闻,其实所谓"直接写屏"指得就是程序将要显示的数据直接送入显示缓冲区中。

到此为止我们已经学完了有关"Video RAM"的知识,其最重要内容无非是如何计算偏移量而已。至于如何让程序的输出效果更有吸引力,那就要看你的想象力有多丰富了。我可以帮你掌握已知,但开创未知恐怕就要看你自己的了。