← 返回 计组与微机控制

计组与微机控制

7.5 算术类指令

7.5 算术类指令

一.算术运算类指令

8086/8088 支持:

加法 减法 乘法 除法加 1 / 减 1求补 比较BCD 调整符号扩展

这部分开始,标志位非常重要。书上表 4.8 里面的符号大意是:

7.5 算术类指令 图 1

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 调整指令。