二、 算术运算类指令
这类指令包括加、减、乘、除4种指令。不过注意两个操作数不能同时为存储器操作数,且目的操作数不能是立即数(不然你结果放哪儿?)。
指令名 |
指令格式 |
指令功能 |
标志位备注 |
加法指令 |
ADD DEST,SRC |
DEST←(SRC)+(DEST) |
按结果置OF、SF、ZF、AF、PF、CF |
带进位加法 |
ADC DEST,SRC |
DEST←(SRC)+(DEST)+CF |
按结果置OF、SF、ZF、AF、PF、CF |
加1指令 |
INC DEST |
DEST←(DEST)+1 |
按结果置OF、SF、ZF、AF、PF,不影响CF |
减法指令 |
SUB DEST,SRC |
DEST←(DEST)-(SRC) |
按结果置OF、SF、ZF、AF、PF、CF |
带借位减法 |
SBB DEST,SRC |
DEST←(DEST)-(SRC)-CF |
按结果置OF、SF、ZF、AF、PF、CF |
减1指令 |
DEC DEST |
DEST←(DEST)-1 |
按结果置OF、SF、ZF、AF、PF,不影响CF |
乘法指令 |
MUL SRC |
DEST←[AL/AX] DEST←DEST*SRC |
按结果置OF、CF、ZF、AF、PF,不影响SF |
带符号乘法 |
IMUL SRC |
DEST←[AL/AX] DEST←DEST*SRC |
按结果置OF、CF、ZF、AF、PF、SF |
除法指令 |
DIV SRC |
DEST←[AL/AX] DEST←DEST/SRC |
按结果置OF、CF、ZF、AF、PF,不影响SF |
带符号除法 |
IDIV SRC |
DEST←[AL/AX] DEST←DEST/SRC |
按结果置OF、CF、ZF、AF、PF、SF |
求补指令 |
NEG DEST |
DEST←0-(DEST) |
按结果置OF、SF、ZF、AF、PF、CF |
比较指令 |
CMP DEST,SRC |
(DEST)-(SRC) |
按结果置OF、SF、ZF、AF、PF、CF |
1. ADD指令
功能: 加法指令
语法: ADD OP1,OP2
格式: ADD r1,r2
ADD r,m
ADD m,r
ADD r,data
影响标志: C,P,A,Z,S,O
ADD指令是需要重点掌握的指令,它将两个字或字节的操作数相加,结果送到目的操作数。这个指令很重要,特别是它要影响到6个状态标志,所以必须注意以下几点:
第一,参与运算的两个操作数应该同时带符号或不带符号。那么,这里就出现第一个问题了,系统如何区分带不带符号的操作数呢?这个问题先放在这儿。
第二,参与运算的两个操作数必须长度一致。针对8086/8088系统,操作数的长度只有两种:字节和字,即8位和16位。那么,当操作数是字节时如果是带符号,则表示范围为-127~127,不带符号的表示范围为0~255;当操作数是字时,如果是带符号则表示范围为-32767~32767,不带符号的表示范围为0~ 65535。好,第二个问题出现了,系统如何判断操作数是字节还是字呢?
第三,与MOV一样,该指令的操作数可以是通用寄存器、基址或变址寄存器、存储器,但不能同时为存储单元,立即数只能为源操作数,不能作目的操作数。
好了,我们就来集中解决刚才出现的两个问题。其实这两个问题都是一个问题,那就是ADD指令是一个非常非常低级的指令。刚才出现的问题完全不是ADD指令所考虑的范围,它所考虑的,只是如何影响标志寄存器的那些位的值。明白了这一点,这两个问题就迎刃而解。先来回答第二个问题,ADD指令根本不需要判断操作数的长度。因为,比如我执行ADD AX, 85H,而AX又是16位的,那么这个就肯定是字操作,后面的那个立即数85H就把前面的字节全用0不齐就OK了。如果要进行字节操作,你可以执行ADD AL,85H,这不就得了。
回到第一个问题,两个数是否是带符号的数也是不需要判断的。我们知道,8086/88088体系是一个补码体系。这里补充一下补码的规则,不管是字节补码还是字补码,其最高位为1则是负数,为0则为正数。前面已经说了,不需要判断操作数的长度,所以这里我们不妨设为字节操作数(字操作数也是一样的):ADD AL,B5H,AL中的值为77H。若看成无符号数,则运算结果理论为12CH。但是,由于是字节操作数,最高位产生进位并自动丢失,所CF产生1,于是实际结果为2CH+CF。当然,你不能说这个结果是不正确的,后面ADC指令会谈到。
若看成有符号数,则77H是正数,因为翻译成二进制为01110111,符号位为0。B5H对应二进制数为10110101,是负数,根据补码规则,这个数是4BH的补码。忘了补码规则?好吧我来教教你:先把4BH换算成二进制:01001011,再求个反:10110100,再加1:10110101不就是B5H了吧。于是乎,ADD AL,B5H其意思其实是77H-4BH,结果等于2CH。
我们看到,不管是看成有符号还是无符号,结果是不是都是2CH?第一个问题就完美的解决了。不过前面都是以例子来解释这个问题的,缺少理论的支撑。要总结出一个理论来,不是那么容易,还是同学们自己去体会吧。这里,再最后提一下,ADD是个很低级的指令,它并不会做那些复杂的逻辑判断,只会去影响那些状态寄存器的某些标志:CF=1则说明在如果是无符号运算,则有进位;SF=1则说明如果是有符号运算时,结果为负,即最高位为1;OF=1则不管是否有符号运算,只要有溢出就置1。最后再注意一下,CF和OF很容易搞混淆,网上对他们的解释也是五花八门,其实很简单,CF是仅仅是针对无符号运算,但OF并不是只针对有符号运算,而是n位字长,也就是跟符号无关。不管是否是有符号操作数,只要结果超过了-2n-1~2n-1就置1,就这么低级。我们的例子中,CF置1了,说明有进位;而OF肯定为0,因为最后的结果2CH没有超过7F,也就是十进制的127。当然,PF、AF和ZF太简单,就不去解释了。
2. ADC指令
功能: 加法指令
语法: ADC OP1,OP2
格式: ADC r1,r2
ADC r,m
ADC m,r
ADC r,data
影响标志: C,P,A,Z,S,O
这是一条带进位的加法指令,其操作与ADD基本相同,唯一的差异是若在ADC指令执行前CF已置位,则在两操作数只和中再加1。
这条指令主要用于多字节加法运算。我们知道,ADD只支持字长8位和16位的操作数,而我们计算机的科学计算有可能涉及到成百上千位的计算,那就会变成多个字节运算。例如有一个32位带符号整数存放在AX(高16位,带符号)和BX(低16位,不带符号),现在要加上一个常数276425H,这时可用下面两条指令实现:
ADD BX,6245H
ADC AX,27H
其中第一条把16位常数6245H加在BX中,如果它产生进位,即CF=1,则在第二条指令完成高16位加法时,用ADC指令就同时把低16位的进位一起加上了。
3. INC指令
功能: 把OP的值加一
语法: INC OP
格式: INC r/m
影响标志: P,A,Z,S,O
这是一条常用的单操作数指令,注意,它不会影响CF状态,即到了FFH或FFFFH后,有循环回0。操作数可以是通用寄存器、基址或变址寄存器、存储器,但不能是段寄存器或立即数。
4. SUB指令
功能:减法指令
语法: SUB OP1,OP2
格式: SUB r1,r2
SUB r,m
SUB m,r
SUB r,data
SUB m,data
影响标志: C,P,A,Z,S,O
SUB指令与ADD指令类似,我们还是举例说明:
MOV AX, 65A0H
MOV BX, B79EH
SUB AX,BX
若操作数看成是两个无符号数,目的操作数小于源操作数,则有借位,CF置1;而运算结果为AE02H,其最高位为1,属于补码,SF置1,其原码为-51FE。至于如何又补码换算成原码,这里不再赘述。
若操作数看成是两个有符号数,则参加运算的两个数为补码,其中源操作数是正数,目的操作数B79EH是一个负数的补码,那么一个正数减去一个负数就该是个正数了,而这里运算结果AE02H显然是个负数,这不扯淡吗?别着急,这个结果显然已经超过16位数的补码的,OF置1。
5. SBB指令
功能:带借位减法指令
语法: SBB OP1,OP2
格式: SBB r1,r2
SBB r,m
SBB m,r
SBB r,data
SBB m,data
影响标志: C,P,A,Z,S,O
带借位的减法指令,同ADC类似,用于多字节的减法运算。例如有一个32位无符号整数00D50017H已存放在AX(高16位)和BX(低16位)中,现在要去减去一个存放在CX(高16位)和DX(低16位)中的一个32位常数00450048H,这时,可以用以下两条指令实现:
SUB BX,DX
SBB AX,CX
6. DEC指令
功能: 把OP的值或减一
语法: DEC OP
格式: DEC r/m
影响标志: P,A,Z,S,O
7. NEG指令
功能: 将OP的符号反相(取二进制补码)
语法: NEG OP
格式: NEG r/m
影响标志: C,P,A,Z,S,O
注意,NEG指令执行取补码运算,即求反加一运算。操作数可以是通用寄存器、基址或变址寄存器、存储器,但不能是段寄存器或立即数。若指令在执行时,操作数的值为-128(字节)或-32768(字),其结果不变但OF要置位;CF总被置为1,除非操作数为0,0的补码还是0。
8. CMP指令
功能: 比较OP1与OP2的值
语法: CMP r/m,r/m/data
标志位: C,P,A,Z,O
CMP指令也是一个重点指令,用得非常多,用于两个操作数的比较,其方法是用目的操作数去减去源操作数,但结果不送回目的操作数,两操作数保持不变,只是标志位发生相应的改变。
9. MUL指令
功能: 无符号数乘法指令
语法: MUL OP
格式: MUL r/m IMUL r/m
影响标志: C,P,A,Z,O(注,不会影响S标志)
乘法指令MUL是单操作数的,执行无符号数乘法。MUL的规则是由累加寄存器(AL或AX)中的数作为乘数,操作数作为被乘数。但是注意,结果并不存放在操作数所对应的那个存储介质中。如果操作数是字节,则与来自AL的乘数相乘,乘积是字,放在AX中;如果操作数是字,则与来自AX的乘数相乘,乘积是双字,放在AX和DX中。
注意,操作数不能是立即数,但可以是来自AX或DX的字操作数,这样导致的结果无非就是乘方而已。
10. IMUL指令
功能: 带符号数乘法指令
语法: IMUL OP
格式: IMUL r/m
影响标志: C,P,A,Z,S,O(注,会影响S标志)
IMUL和MUL类似,不在赘述,这里提一下,乘积(结果)的高位(字节相乘来自AH,字相乘来自DX)如果不全为0(MUL指令);或是低位(AL或AX)的符号被置1,则CF=1,OF=1,表示AH或DX中含有乘积的有效位。
例如,AL=0B4H,BL=11H,执行MUL BL后,AX=0BF4H,即十进制数的3060;而执行指令IMUL BL后,AX=0FAF4H,即十进制数的-1292。这是因为AL中的0B4H在看成有符号数是-76,无符号数则是180;BL=11H,不管有符号还是无符号都是十进制数中的17。
11. DIV指令
功能:不带符号数除法指令
语法: DIV OP
格式: DIV r/m
DIV规定用累加器中的内容作为被除数,除以位于指令中的源操作数。如果源操作数是8位字节数据,则要求被除数为16位,须放在AX中,所得商放在AL中,余数放在AH中;如果源操作数是16位字数据,那么就要去被除数为32位,放在DX和AX中,所得的商放在AX中,余数在DX中。
这里有个著名的问题,如果商超过了AL或AX的最大值怎么办?有人会问了,会有这种情况吗。其实,这就是单指操作数为0的情况,这就会触发著名的0号中断。
12. IDIV指令
功能:带符号数除法指令
语法: IDIV OP
格式: IDIV r/m
13. CBW和CWD 指令
功能: 有符号数扩展指令
语法: CBW
CWD
如果是带符号的数据进行除法,会有个问题,有时需要把短位数的被除数转换成位数更长的数据类型。比如,要用BL中的数据去除AL,但根据除法指令的规定:除数是8位,则被除数必须是AX,于是就涉及到AH的取值问题。
为了方便说明,假设:(AH)=1H,(AL)=90H=-112D,(BL)=10H。假设在作除法时,不管AH中的值,这时,(AH、AL)/BL的商是19H,但我们知道:AL/BL的商应是-7,这就导致:计算结果不是所预期的结果,所以,在作除法运算前,程序员必须要处理AH中的值。
那么,作无符号数除法时,可强置AH的值为0,于是,可得到正确的结果。但是,如果作有符号数除法时,如果强置AH为0,则AX=0090H,这时,AX/BL的商为9,显然结果也不正确。看出问题来没有,如果把AL的符号位1,扩展到AH中,得:AX=0FF90H=-112D,这时,AX/BL的商就是我们所要的正确结果。
所以,CBW和CWD就闪亮登场了,CBW是将AL中的数的符号扩展到AH寄存器中,而CWD是将AX的符号扩展到DX寄存器中。
14. AAA,AAS,AAM,AAD指令
功能: 非压BCD码运算调整指令
语法: AAA AAS AAM AAD
影响标志: A,C(AAA,AAS) S,Z,P(AAM,AAD)
15. DAA,DAS指令
功能: 压缩BCD码调整指令
语法: DAA DAS
影响标志: C,P,A,Z,S