汇编语言程序设计
上QQ阅读APP看书,第一时间看更新

第三节 理解与练习

一、关于十进制调整指令

在计算机中,十进制数使用BCD码来表示。计算机可以对BCD码进行加、减、乘、除运算。8086/8088采用的方法是利用对普通二进制的运算指令算出结果,然后用专门的指令对结果进行调整;或者反过来,先对数据进行调整,再用二进制数指令进行运算。

那么为什么用普通二进制数据运算指令对BCD码运算时,要进行调整呢?又怎样进行调整呢?

下面通过例子说明十进制调整的原理。

比如,8+7=15,用BCD码表示,运算结果为:

即结果为0FH。在BCD码中,只允许0~9这10个数字出现,0FH不代表任何BCD码,因此要对它进行变换。

怎样变换呢?我们知道BCD码应该是逢10进1,但计算机在这里是逢16进1。因此,可以在个位上补一个6,让其产生进位,而此进位作为十位数出现。即

可见在BCD码运算结果中,如果1位BCD码所对应的4位二进制码超过9,那就应该补上一个6产生进位来进行调整。

又如,9+9=18,用组合的BCD码表示时,运算过程为:

结果为12,这显然是错误的。为什么会得到错误的结果呢?原因就是计算机在运算时,如遇到低4位往高4位产生进位,那么是按逢16进1的规则进行的。但BCD码要求逢10进1,可见,当BCD码按二进制数运算时,只要产生了进位,就会“暗中”丢失一个6。于是,应该在出现进位时,进行如下调整:

由上述可知,BCD码调整的规则是:当运算结果对应一位BCD的4位二进制数大于9,或遇到低4位往高4位有进位,就必在低4位加6进行调整。计算机运算时,如果低4位往高4位有进位,则辅助进位位AF=1,所以,对这种情况进行调整时,只要把辅助进位位作为判断依据就行了。

当对多个字节进行BCD码运算时,调整的原理是类似的。如果低位字节往高位字节产生进位,则CF为1;而当1个字节中的低4位往高4位产生进位时,AF为1。十进制调整指令会根据CF和AF的值判断是否进行“加6调整”,并进行具体的调整操作。然后程序再对高位字节进行运算,并进行十进制调整。

上面就是对BCD码进行加、减和乘法运算时作十进制调整的原理和思想。对于BCD码的除法运算,十进制调整过程与此有所差别,将在后面再作说明。下面来具体说明BCD码的有关指令。

(一)BCD码的加法十进制调整指令

对BCD码的加法进行十进制调整的指令有两条,即AAA(ASCII Adjust for Addition)和DAA (Decimal Adjust for Addition)。前者用于对两个非组合的BCD码相加后的结果进行调整,产生一个非组合的BCD码;后者用于对两个组合的BCD码相加结果进行调整,产生一个组合的BCD码。这两条指令使用时,都紧跟在加法指令之后,对BCD码加法运算结果进行调整。

用非组合的BCD码时,1个字节只用低4位来表示1位BCD码,高4位为0。比如用00000111表示7,用00000101表示5,这两个BCD码相加后,应为12,即AH中为0000001,而AL中为00000010。但是,在调整之前,得到的结果为:

然后执行调整指令AAA,因为低4位超过9,所以调整时先往低4位加6,以产生进位,即:

AL的高4位为1,使AH+1→AH,即AH中为00000001,然后,将AL中的内容与0FH相“与”,最后,AX中得到非组合的BGD码0000000100000010。

AAA指令会影响标志位AF和CF,但是OF、PF、SF和ZF在执行AAA指令后没有意义。

用组合的BCD时,1个字节可以表示2位BCD码。两个组合的BCD数据相加后,用DAA指令进行调整,调整原理和AAA类似。DAA指令会影响AF、CF、PF、SF和ZF,而对OF来说,执行DAA指令后可为0,也可为1,但没有意义。

【例3.3】 下面是一个对2个8字节长的组合的BCD码数据进行相加,设第一个数据在1000H开始的8个内存单元中,第二个数据在2000H开始的8个内存单元中,要求相加之后将结果放在2000H开始的内存区域。具体程序段如下:

(二)BCD码的减法十进制调整指令

对BCD码数据进行减法运算后进行调整的指令也有两条,它们分别为AAS(ASCII Adjust for Substraction)和DAS(Decimal Adjust for Substraction)。前者对两个非组合的BCD码数据的相减给果进行调整,产生一个非组合的BCD码形式的差。后者对两个组合的BCD码数据的相减结果进行调整,得到一个组合的BCD码的差。

使用时,AAS和DAS指令也是紧跟在减法指令后面。 AAS和AAA的功能类似,对标志位的影响也和AAA指令相同,即影响AF、CF,但OF、PF、SF和ZF在执行AAS指令后不确定。

DAS指令则和DAA指令的功能类似,此指令也影响AF、CF、PF、SF和ZF,但OF标志在执行DAS后没有意义

(三)BCD码的乘法十进制调整指令

对BCD码数据进行乘法运算时,要求乘数和被乘数都用非组合的BCD码来表示,否则得到的结果将无法调整。由于这个道理,8086指令系统中只提供了对于非组合的BCD码相乘结果的十进制调整指令AAM(ASCII Adjust for Multiply)。

AAM指令也是紧跟在乘法指令MUL之后的,它对两个非组合的BCD码相乘结果进行调整,最后得到一个正确的非组合的BCD码结果。例如:AL=00011100B,执行AMM后AH=00000010,AL=00001000,即得到一个非组合十进制数28。

注意,BCD码总是作为无符号数看待的,所以相乘时用MUL指令,而不用IMUL指令。

利用AAM指令,可以方便地将AL中小于100的二进制数直接转换成非组合十进制数。

【例3.4】 将AL中的二进制结果(值小于100)以十进制形式显示在屏幕上。程序如下:

(四)BCD码的除法十进制调整指令

对BCD码进行除法十进制调整的指令为AAD(ASCII Adjust for Division)。对BCD码进行除法运算时,也要求除数和被除数都用非组合的BCD码形式来表示,这是与对BCD码乘法的要求类似的地方。这里要特别注意一点,对BCD码除法运算的调整是在进行除法之前,通过对除数和被除数进行调整来实现的。

对除数和被除数的调整过程本身比较简单。比如,一个数据为65,用非组合的BCD码表示,则AH中为00000110,AL中为00000101,调整时执行AAD指令,这条指令将AH中的内容乘以10,再加到AL中,这样,得到的结果为41H。

二、乘除法指令的理解

为什么要对无符号数和有符号数提供两种不同的乘法指令呢?看一个简单的例子。

例:3×(-2)=-6,因机器中带符号数用补码表示,-2被表示为1110,因此有:

再看一个式子:3×14=42(2AH),计算如下:

两个不同的数相乘,却得到相同的结果。对3×14来说是正确的,但对于3×(-2)却是错误的。如果用另一种方法来计算,即先将-2去掉符号位,计算3×2后,再添上符号位,即取结果的补码。则为:

再取补码得:11111010=FAH=-6

这个结果对于3×(-2)是正确的,但对于3×14是错误的。

可见,在执行乘法运算时,要想使无符号数相乘得正确的结果,则在做有符号数相乘时,就得不到正确结果;反之亦然。为了使两种情况下分别获得正确的结果,于是8086/8088对无符号数相乘和有符号数相乘提供了不同的乘法指令MUL及IMUL。实际上,3×14例子中体现的就是MUL指令的执行过程,而3×(-2)的例子中体现的就是IMUL指令的执行过程。

IMUL指令的执行过程如下。

(1)将参加乘法运算的数取补(NEG),使负数变为正数。

(2)进行二进制乘法运算。

(3)将运算结果(积)再取补。

对于除法运算,也有同样的情况。因此,8086/8088指令系统中也有对无符号数的除法指令和对有符号数的除法指令。

8086/8088执行除法运算时,规定除数必须为被除数的一半字长,即被除数为16位,除数为8位,被除数为32位时,除数为16位。16位的被除数放在AX中,当被除数为32位时,则放在DX和AX中,DX中放高16位,AX中放低16位,即把DX看成是AX的扩展。指令格式中给出的是除数的长度和形式,计算机根据给定的除数为8位还是16位来确定被除数为16位还是32位。

被除数为16位,除数为8位时,得到8位的商放在AL中,8位的余数放在AH中。当被除数为32位,除数为16位时,得到16位的商放在AX中,16位的余数放在DX中。

对除法指令,有几点需要指出。

(1)除法运算后,标志位AF、CF、OF、PF、SF和ZF都是不确定的,也就是说,它们或为0,或为1,但都没有意义。

(2)用IDIV指令时,如果是一个双字除以一个字,则商的范围为-32768~+32767,如果是一个字除以一个字节,则商的范围为-128~+127。如果超出了这个范围,那么,会作为除数为0的情况来处理,即产生0号中断(除法错),而不是按照通常的想法使溢出标志0F置1。因此要求除数必须足够大。

(3)在对有符号数进行除法运算时,比如-30除以+8,可以得到商为-4余数为+2,也可以得到商为-3,余数为-6。这两种结果都是正确的,前种情况的余数为正数,后种情况的余数为负数。8086/8088指令系统中规定余数的符号和被除数的符号相同,因此,对这个例子,会得到后一种结果。

(4)除法运算时,要求用16位数除以8位数,或者用32位数除以16位数,当被除数只有8位时,必须将此8位数据放在AL中,并对高8位AH进行扩展。同样,当被除数只有16位,而除数也为16位时,必须将16位被除数放在AX中,并对高16位DX进行扩展。如果在这些情况下,没有对AH或DX进行扩展,那就会得到错误的结果。

对无符号数相除来说,AH和DX的扩展很简单,只要将这两个寄存器清0就行了。对有符号数相除来说,AH和DX的扩展就是低位字节或低位字的符号扩展,即把AL中的最高位扩展到AH的8位中,或者把AX中的最高位扩展到DX的16位中。

为此,8086/8088指令系统提供了专用于对有符号数进行符号扩展的指令CBW和CWD。

三、逻辑运算与移位指令的应用

在程序设计中,逻辑运算指令的使用频度较高,要灵活地使用好逻辑运算指令,首先要掌握各种逻辑运算的规律。

(一)自身相与或者相或不变

这个规律常有如下应用:

     AND  AX,AX
     OR  BX,BX

类似这样的指令常用于如下两种情况。

(1)在多字节运算前清进位标志。

(2)在不改变寄存器的值的情况下,为其后的转移判断指令设置标位状态,当然这种情况可以使用测试指令TEST。

(二)自身异或清0

如:

     XOR CX,CX

该指令常在初始化时使某个寄存器清零,同时使进位标志CF=0(也使OF=0)。

(三)和0相与的位清0,和1相与的位不变

这个规律常用在对某一个数据,使它的某些位清0,使某些位不变的场合。如:

     AND AL,OFH

该语句使AL高4位清0,而低4位保持不变,再如,要使AL中8位数据的D3、D4位清0,而其余位不变,可以使用语句:

     AND AL,OE7H

这里OE7H叫作掩码。

(四)和1相或的位置1,和0相或的位不变

这个规律常用来使一个数据的某些位置1,其他位不变的场合。例如,将AL的D3、D4位置1,其余位不变,可以使用语句“OR AL,18H”来完成。

和1异或的位取反,和0异或的位不变。这个规律常用在使一个数据的某些位取反,其他位不变的场合。例如,将AL中数据的D3、D4位取反,其余位不变,可以使用下述语句实现:

     XOR  AL,18H

【例3.5】 编程将AL中的第i,i+1位写入MEM字节第i,i +1位,其他位的内容不变。

参考答案:

【例3.6】 DX:AX中的双字逻辑左移1位。

分析:移位指令只能对字节或字操作。对多字长数据的移位,就要多次使用移位指令。但每次移位,都要丢失1位被移出的数据。解决的办法是:第一次使用移位指令,其后使用带进位的循环指令。

参考程序:

【例3.7】 把DX:AX中的双字逻辑左移4位。

参考程序:

【例3.8】 编程将AL中的第7位和第0位,第6位和第1位,第5位和第2位,第4位和第3位互换。

参考程序:

【例3.9】 编程将AL中的两位压缩的BCD码分解成非压缩BCD码分别存入DH和DL中。

参考程序:

另外,还有一种更简单的方法能实现以上功能:

四、指令对标志位的影响

掌握每一条指令对标志位的影响,对程序设计是非常重要的。现将指令执行后对标志位的影响做一下总结。

(一)指令对标志位的影响

除算术运算类指令、逻辑运算指令、移位和循环移位指令影响标志位,其他指令一般对标志位无影响。

有些指令对标志位的影响有特殊的规定,具体如下。

(1)在算术运算指令中,除法指令不产生有效的标志;乘法指令仅影响CF和OF,而其他状态标志位无效;加1指令INC和减1指令DEC不影响进位标志CF,这一点需要特别注意。

(2)逻辑运算指令除NOT外,都使进位标志CF和溢出标志OF为0。

(3)循环指令仅影响进位标志CF和溢出标志OF。

(4)串操作指令中仅CMPS和SCAS两条指令影响标志位,这两条指令可视为算术类指令。

(二)状态标志位的变化规律

(1)符号标志SF:总是和运行结果的最高位的状态一致。

(2)零标志ZF:运行结果为0,ZF=1;否则ZF=0。

(3)奇偶标志PF:结果的低8位有偶数个1,PF=1;否则为0。

(4)溢出标志OF:算术运算类指令执行后,当结果的最高位与参加运算的数据的最高位不一致时,OF=1,否则OF=0。

特殊情况:

• 乘法指令,当乘积的高半部分不为0时OF=1,否则为0,对除法指令OF无效。

• 移位与循环指令执行后,最高位状态若改变OF=1;否则为0。

• 逻辑运算指令总是使OF=0。

(5)进位标志CF:算术运算指令执行后,若产生进位或借位,CF=1;否则为0。但INC与DEC不影响该标志。

特殊情况:

• 取补指令NEG使CF=1,仅当对0取补时CF=0。

• 乘法指令当乘积的高半部分不为0时,CF=1;否则为0。这一点和OF相同,对除法指令CF无效。

• 逻辑运算指令总使CF=0,但NOT指令例外,它不影响任何标志。

• 移位与循环指令执行后,CF等于移入位的状态。

寻址方式与指令系统.ppt