计组与微机控制
7.5 算术类指令
7.5 算术类指令
一.算术运算类指令
8086/8088 支持:
加法 减法 乘法 除法加 1 / 减 1求补 比较BCD 调整符号扩展
这部分开始,标志位非常重要。书上表 4.8 里面的符号大意是:

O:该指令会影响这个标志位-:不影响这个标志位*:标志位结果不确定1:该标志位置 1
算术运算经常影响:CF、PF、AF、ZF、SF、OF
这些标志位后面会配合条件转移使用,例如:
JZ JNZ JC JNC JS JNS JO JNO
所以学算术指令,不能只看结果,还要看 FLAGS。
二、ADD 和 SUB
ADD 是加法,SUB 是减法。
格式:
ADD dest, src SUB dest, src
功能:
ADD: dest ← dest + src SUB: dest ← dest - src
注意结果送回目的操作数 dest。源操作数 src 不变。
1. ADD/SUB 的常见形式
书上总结了几种:
ADD reg, reg/mem ADD mem, reg ADD reg/mem, imm
SUB reg, reg/mem SUB mem, reg SUB reg/mem, imm
意思是:目的操作数可以是寄存器或内存,源操作数可以是寄存器、内存或立即数但不能两个操作数同时是内存,目的操作数不能是立即数
例如合法:
ADD AX, BX ADD AX, [SI] ADD [SI], AX ADD AX, 1234H ADD WORD PTR [SI], 1
不合法:ADD [1000H], [2000H] ADD 1234H, AX
2. 操作数长度必须一致
例如:ADD AX, BX两个都是 16 位,合法。
ADD AL, BL两个都是 8 位,合法。
但:ADD AX, BL不合法。
3. ADD/SUB 会影响哪些标志位?
ADD/SUB 会影响:CF、PF、AF、ZF、SF、OF
其中最重要的是:
CF:无符号进位/借位OF:带符号溢出ZF:结果是否为 0SF:结果符号位
例 4.9:ADD BX, AX
书上给:AX = 65A0H BX = B79EH
执行:ADD BX, AX
意思是:BX ← BX + AX
计算:B79EH + 65A0H = 11D3EH
16 位寄存器 BX 只能保存低 16 位,所以:BX = 1D3EH
最高位产生了进位,所以:CF = 1
1. 如果按无符号数看
AX = 65A0H,是一个无符号正数。
BX = B79EH,也是一个无符号数。
它们相加结果超过 16 位范围,所以:CF = 1
说明无符号加法溢出了。
2. 如果按带符号数看
16 位带符号数看最高位。
AX = 65A0H,最高位是 0,所以是正数。
BX = B79EH,最高位是 1,所以是负数。
正数加负数,一般不会发生带符号溢出,所以:OF = 0
这就体现了:同一串二进制,按无符号数看和按带符号数看,结论可能不同
三、ADC 和 SBB
ADC 是带进位加法。SBB 是带借位减法。(之前arm系统都讲过看一下格式区别吧)
格式:
ADC dest, src SBB dest, src
功能:
ADC: dest ← dest + src + CFSBB: dest ← dest - src - CF
这里的 CF 是之前低位运算产生的进位或借位。
1. 为什么需要 ADC/SBB?
因为 8086 是 16 位 CPU,但你可能要处理 32 位、64 位数据。
比如做 32 位加法,需要分成低 16 位和高 16 位分别处理。
低 16 位相加可能产生进位,这个进位要加到高 16 位里。这时就要用 ADC。
同理,32 位减法时,低 16 位不够减产生借位,高 16 位要减掉这个借位,这时用 SBB。
例 4.10:32 位数相减 Z = X - Y
书上例子:X、Y、Z 都是 32 位数
每个 32 位数占 4 个字节,也就是两个 16 位字:低 16 位 高 16 位
代码是:
MOV AX, WORD PTR X SUB AX, WORD PTR Y MOV WORD PTR Z, AX MOV AX, WORD PTR X+2 SBB AX, WORD PTR Y+2 MOV WORD PTR Z+2, AX
1. 先算低 16 位
MOV AX, WORD PTR X SUB AX, WORD PTR Y MOV WORD PTR Z, AX
这三条做的是:Z 的低 16 位 = X 的低 16 位 - Y 的低 16 位
如果低 16 位不够减,SUB 会让 CF = 1。
2. 再算高 16 位
MOV AX, WORD PTR X+2 SBB AX, WORD PTR Y+2 MOV WORD PTR Z+2, AX
这里用 SBB,不用 SUB。
因为如果低 16 位发生借位,CF = 1,高 16 位要再减 1。
所以:高 16 位 = X 高 16 位 - Y 高 16 位 - CF
这就是 SBB 的作用。
3. 这个思想非常重要
多字节运算的基本套路:低位先算,高位后算用 CF 传递进位或借位
加法用:ADDADC
减法用:SUB SBB
四、INC 和 DEC
INC 是加 1。DEC 是减 1。
格式:
INC dest DEC dest
功能:
INC: dest ← dest + 1DEC: dest ← dest - 1
例如:
INC SI DEC CX
常见用途:修改地址指针,修改循环计数器
1. INC/DEC 的操作数
操作数可以是:8 位寄存器 16 位寄存器 内存单元
例如:
INC AX INC AL INC WORD PTR [SI] DEC CX
不能是立即数:INC 5 不合法。
2. INC/DEC 不影响 CF
这个很重要。INC/DEC 会影响:
PF、AF、ZF、SF、OF
但不影响:CF因为 INC/DEC 常用于循环计数和地址调整,如果每次都改变 CF,就会破坏多字节加减法中的进位/借位状态。
所以:INC AX哪怕 AX 从 FFFFH 变成 0000H,CF 也不会因此改变。
五、NEG 求补指令
NEG 是 Negative,求负。
格式:NEG dest
功能:dest ← 0 - dest
也就是把操作数变成相反数。
例如:AL = 05H (0000 0101) NEG AL
执行后:AL = FBH(1111 1011)补码即使取反加1(0000 0100)之后加1(0000 0101)就是5,但是原本有符号位1,表示负数
在 8 位补码中,FBH 表示 -5。
1. NEG 的本质是求补码
机器里带符号数用补码表示。要求一个数的相反数,就是求它的补码。(只针对负数而言,正数的补码是自己,负数的补码是取反加1)
例如 8 位中:0000 0101B = +5
取反加 1:1111 1010B + 1 = 1111 1011B
也就是 FBH,表示 -5。所以 NEG 可以理解成:取反加 1
但从指令定义上看,是:0 - 操作数
2. NEG 对标志位的影响
NEG 会影响所有算术状态标志。特别注意 CF:
如果操作数原来是 0,NEG 后 CF = 0如果操作数原来不是 0,NEG 后 CF = 1
因为:0 - 非零数,需要借位。还有一个特殊情况:
8 位最小负数 -12816 位最小负数 -32768
它们取负后仍然无法表示。
例如 8 位:-128 的补码 = 80H
取负还是 80H,因为 +128 超出了 8 位带符号数范围。
这时:OF = 1
六、例 4.11:求一组带符号数的绝对值
书上例子是:AREA1 中有 100 个带符号数,把它们的绝对值存到 AREA2
程序:
LEA SI, AREA1 LEA DI, AREA2 MOV CX, 100 CHECK: MOV AL, [SI] OR AL, AL JNS NEXT NEG AL NEXT: MOV [DI], AL INC SI INC DI DEC CX JNZ CHECK HLT
我们逐句读。
1. 初始化指针
LEA SI, AREA1 LEA DI, AREA2
意思是:SI 指向源数组 AREA1,DI 指向目标数组 AREA2
然后:MOV CX, 100
CX 作为循环计数器,表示处理 100 个数。
2. 取一个数
MOV AL, [SI] 从 AREA1 当前元素取一个字节到 AL。
因为题目说是 100 个带符号数,一般这里按字节带符号数处理。
3. 判断正负
OR AL, AL这个操作很巧。
AL 和自己 OR,结果还是 AL 本身,所以 AL 的内容不变。
但是 OR 会影响标志位,尤其是 SF 和 ZF。
如果 AL 最高位是 1,说明它是负数,执行 OR 后:SF = 1
如果 AL 最高位是 0,说明它是非负数:SF = 0
然后:JNS NEXT
JNS 是 Jump if Not Sign,意思是:如果 SF = 0,就跳到 NEXT
也就是如果这个数不是负数,就不用求补,直接保存。
4. 如果是负数,就求补
NEG AL把负数变成它的相反数,也就是绝对值。
例如:AL = F6H F6H 是 -10。
执行 NEG 后:AL = 0AH也就是 +10。
5. 保存结果并移动指针
MOV [DI], AL把绝对值写入目标数组。
INC SI INC DI
两个指针都加 1,指向下一个元素。
DEC CX JNZ CHECK
处理完一个数,循环次数减 1。
如果 CX 不为 0,继续循环。
如果 CX = 0,说明 100 个数都处理完了。
七、CMP 比较指令
CMP 是 Compare。
格式:CMP dest, src
功能:计算 dest - src但不保存结果只影响标志位
它本质上和 SUB 类似,只是结果不写回 dest。
例如:
CMP AX, BX
CPU 内部做:
AX - BX然后根据结果设置 FLAGS。
但 AX 和 BX 的内容都不变。
1. CMP 常用于条件转移前
例如:
CMP AX, BX JE EQUAL JA ABOVE JB BELOW
意思是先比较 AX 和 BX,再根据标志位决定跳不跳。
2. 判断相等
如果 dest - src = 0则 ZF = 1
所以:CMP AX, BX JZ SAME
或者:CMP AX, BX JE SAME
表示 AX 和 BX 相等就跳转。
3. 无符号数比较看 CF
对于无符号数:
dest < src → CF = 1dest ≥ src → CF = 0
例如:
CMP AX, BX JB SMALLER
JB 是 Jump if Below,用于无符号小于。
4. 带符号数比较看 SF 和 OF
这里要特别提醒一下:带符号比较的标准规则是看:SF ⊕ OF
如果:SF ⊕ OF = 1
说明:dest < src
如果:SF ⊕ OF = 0
说明:dest ≥ src
所以带符号小于常用:JL
带符号大于常用:JG
课本这一行如果你看到写成 “OF 和 CF”,从 8086 标志位原理上看,带符号比较应当是 SF 和 OF;CF 是给无符号比较用的。
八、MUL 和 I MUL 乘法指令
接下来是乘法。8086 的乘法指令有两个:
MULIMUL
区别是:
MUL:无符号乘法IMUL:带符号乘法
1. MUL/I MUL 都只有一个显式操作数
例如:MUL BL I MUL BX
看起来只有一个操作数,但另一个操作数是隐含的。
2. 8 位乘法
如果源操作数是 8 位:MUL reg8/mem8
那么隐含:AL × 源操作数
结果放在 AX 中:AX ← AL × src8
例如:
MOV AL, 12H MOV BL, 10H MUL BL
执行的是:AX = AL × BL
3. 16 位乘法
如果源操作数是 16 位:MUL reg16/mem16
那么隐含:AX × 源操作数
结果是 32 位,放在:DX:AX
也就是:DX 放高 16 位 AX 放低 16 位
例如:MUL BX
执行的是:DX:AX = AX × BX
4. 为什么结果要用两倍长度?
因为两个 16 位数相乘,结果最多可能是 32 位。
例如:FFFFH × FFFFH
结果肯定超过 16 位。
所以 8086 用 DX:AX 保存完整结果,避免乘法结果丢失。
九、MUL 对标志位的影响
MUL 主要影响:CF 和 OF
对于无符号乘法:
如果结果的高半部分为 0,则 CF = OF = 0如果结果的高半部分不为 0,则 CF = OF = 1
8 位乘法:结果在 AX如果 AH = 0,则 CF=OF=0如果 AH ≠ 0,则 CF=OF=1
16 位乘法:结果在 DX:AX如果 DX = 0,则 CF=OF=0如果 DX ≠ 0,则 CF=OF=1
意思是:高半部分有有效数字,说明低半部分装不下完整结果
十、I MUL 对标志位的影响
I MUL 是带符号乘法。它也主要看 CF 和 OF。但判断标准稍微不同。
对于带符号乘法,如果高半部分只是低半部分符号位的扩展,则说明低半部分已经能正确表示结果。
这时:CF = OF = 0
否则:CF = OF = 1
举个直观例子:
如果 16 位乘法结果是一个可以用 AX 正确表示的带符号数,那么 DX 只应该是 AX 符号位的扩展:
AX 是正数 → DX 应该是 0000HAX 是负数 → DX 应该是 FFFFH
如果不是这样,说明 DX 中包含了有效结果,AX 单独装不下。
十一、DIV 和 I DIV 除法指令
除法指令有两个:
DIVIDIV
区别:DIV:无符号除法 IDIV:带符号除法
和乘法一样,它们也只有一个显式操作数。这个操作数是:除数
被除数是隐含的。
1. 8 位除法
如果除数是 8 位:DIV reg8/mem8
那么被除数隐含为 AX。
执行:AX ÷ src8
结果:AL = 商AH = 余数
例如:
MOV AX, 1234H MOV BL, 12H DIV BL
意思是:AX ÷ BL商放 AL,余数放 AH
2. 16 位除法
如果除数是 16 位:DIV reg16/mem16
那么被除数隐含为 DX:AX。
执行:DX:AX ÷ src16
结果:AX = 商 DX = 余数
例如:DIV BX
意思是:DX:AX ÷ BX商放 AX 余数放 DX
3. I DIV 类似,只是按带符号数解释
I DIV reg8/mem8
表示:带符号 AX ÷ src8AL = 商AH = 余数
I DIV reg16/mem16
表示:带符号 DX:AX ÷ src16AX = 商DX = 余数
除法最容易出错的点,除法比乘法更容易出问题。
1. 被除数必须提前准备好
8 位除法:被除数必须在 AX
16 位除法:被除数必须在 DX:AX
不能随便写:DIV BX
然后以为 CPU 会拿某个普通寄存器去除。
它一定是:DX:AX ÷ BX
2. 商不能超范围
8 位除法商放 AL,所以商必须能放进 8 位:0 ~ 255
16 位除法商放 AX,所以商必须能放进 16 位:0 ~ 65535
如果商太大,CPU 会产生除法错误中断。
3. 除数不能为 0
这个不用多说,除数为 0 会产生除法错误。
总结:这一批内容可以压缩成几组重点:
1. 地址传送
LEA:取有效地址 EALDS:取远指针,偏移送 reg,段值送 DS LES:取远指针,偏移送 reg,段值送 ES
最重要区别:MOV 取内容 LEA 取地址
2. 标志位传送
LAHF:FLAGS 低 8 位 → AH SAHF:AH → FLAGS 低 8 位PUSHF:FLAGS 入栈 POPF:栈顶 → FLAGS
3. I/O 传送
IN:从端口输入到 AL/AX OUT:从 AL/AX 输出到端口
端口小于 256 可以直接写端口号,大端口地址放 DX。
4. 算术指令
ADD/SUB:普通加减ADC/SBB:带进位/借位加减,用于多字节运算INC/DEC:加 1/减 1,不影响 CFNEG:求补,等价于 0 - 操作数CMP:比较,只影响标志位,不保存结果MUL/IMUL:无符号/带符号乘法DIV/IDIV:无符号/带符号除法
5. 隐含寄存器特别多 乘除法一定要记住隐含寄存器:
8 位乘法:AL × src8 → AX16 位乘法:AX × src16 → DX:AX8 位除法:AX ÷ src8 → AL 商,AH 余数16 位除法:DX:AX ÷ src16 → AX 商,DX 余数
这几条是考试和写程序都非常常用的。下一页应该会继续讲 DIV/I DIV 的注意事项,以及 CBW、CWD、BCD 调整指令。
