11.7 ~ 11.9
《汇编语言(第3版)》11.7 ~ 11.9、《零基础入门学习汇编语言》P56 ~ 57
编程计算 1EF000H+201000H,结果放在 ax(高 16 位)和 bx(低 16 位)中。
mov ax,001EH
mov bx,0F000H
add bx,1000H
adc ax,0020H
adc 指令执行后,也可能产生进位值,所以也会对 CF 位进行设置。
由于有这样的功能,我们就可以对任意大的数据进行加法运算。
例如,编程计算 1EF0001000H+2010001EF0H,结果放在 ax(高 16 位)bx(次高 16 位)cx(低 16 位)中。
mov ax,001EH
mov bx,0F000H
mov cx,1000H
add cx,1EF0H
adc bx,1000H
adc ax,0020H
下面我们,编写一个子程序,对两个 128 位数据进行相加
参数:ds:si 指向存储第一个数的内存空间,因数据为 128 位,所以需要 8 个字单元,由低地址单元到高地址单元依次存放 128 位数据由低到高的各个字。运算结果存储在第一个数的存储空间中。
ds:di 指向存储第二个数的内存空间
assume cs:code,ds:data
data segment
db 88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h
db 11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
call add128
mov ax,4c00h
int 21h
add128:
push ax
push cx
push si
push di
sub ax,ax # 将 CF 设置为 0
s:
mov ax,[si]
adc ax,[di]
mov [si],ax
inc si
inc si
inc di
inc di
loop s
pop di
pop si
pop cx
pop ax
ret
code ends
end start
思考:inc 和 loop 指令不影响 CF 位,上面的程序中,能不能将 4 个 inc 指令,用
add si,2
add di,2
取代?答:不能,因为 add 会影响 CF 位。
11.7 sbb 指令
sbb 是带借位减法指令,它利用了 CF 位上记录的借位值
- 格式:sbb 操作对象 1,操作对象 2
- 功能:操作对象 1= 操作对象 1- 操作对象 2-CF
- 比如:sbb ax,bx
- 实现功能:(ax)=(ax)-(bx)-CF
利用 sbb 指令我们可以对任意大的数据进行减法运算。
计算 003E1000H-00202000H,结果放在 ax,bx 中,程序如下
mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H
sbb 和 adc 是基于同样的思想设计的两条指令,在应用思路上和 adc 类似。在这里,我们就不再进行过多的讨论。
通过学习这两条指令,我们可以进一步领会一下标志寄存器 CF 位的作用和意义。
11.8 cmp 指令
cmp 是比较指令,功能相当于减法指令,只是不保存结果。
cmp 指令执行后,将对标志寄存器产生影响。
其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果
- 格式:cmp 操作对象 1,操作对象 2
- 功能:计算操作对象 1- 操作对象 2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。
- 比如:cmp ax,ax
- 做 (ax)-(ax) 的运算,结果为 0,但并不在 ax 中保存,仅影响 flag 的相关各位。
指令执行后
- ZF=1
- PF=1
- SF=0
- CF=0
- OF=0
下面的指令
mov ax,8
mov bx,3
cmp ax,bx # 执行后:(ax)=8
ZF=0
PF=1
SF=0
CF=0
OF=0
其实,我们通过 cmp 指令执行后,相关标志位的值就可以看出比较的结果。
例如:cmp ax,bx
如果 | 则 | 所以 |
---|---|---|
(ax)=(bx) | (ax)-(bx)=0 | ZF=1 |
(ax)≠(bx) | (ax)-(bx)≠0 | ZF=0 |
(ax)<(bx) | (ax)-(bx) 将产生借位 | CF=1 |
(ax)≥(bx) | (ax)-(bx) 不必借位 | CF=0 |
(ax)>(bx) | (ax)-(bx) 既不必借位,结果又不为0 | CF=0 且 ZF=0 |
(ax)≤(bx) | (ax)-(bx) 既可能借位,结果可能为 0 | CF=1 或 ZF=1 |
现在我们可以看出比较指令的设计思路
即:通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较的结果。
反过来看上面的例子 cmp ax,ax
如果 | 说明 |
---|---|
ZF=1 | (ax)=(bx) |
ZF=0 | (ax)≠(bx) |
CF=1 | (ax)<(bx) |
CF=0 | (ax)≥(bx) |
CF=0 且 ZF=0 | (ax)>(bx) |
CF=1 或 ZF=1 | (ax)≤(bx) |
同 add、sub 指令一样,CPU 在执行 cmp 指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算。
所以利用 cmp 指令可以对无符号数进行比较,也可以对有符号数进行比较。
下面我们再来看一下如果用 cmp 来进行有符号数比较时,我们要注意哪些标志位!
我们以 cmp ah,bh 为例进行说明
如果 | 则 | 所以 |
---|---|---|
(ah)=(bh) | (ah)-(bh)=0 | ZF=1 |
(ah)≠(bh) | (ah)-(bh)≠0 | ZF=0 |
所以我们根据指令执行后 ZF 的值,就可以知道两个数据是否相等。
我们继续看,如果 (ah)<(bh) 则可能发生什么情况呢?
对于有符号数运算,在 (ah)<(bh) 情况下,(ah)-(bh) 显然可能引起 SF=1,即结果为负。
比如:(ah)=1,(bh)=2:则 (ah)-(bh)=0FFH,0FFH 为 -1 的补码,因为结果为负,所以 SF=1。
(ah)=0FEH,(bx)=0FFH:则 (ah)-(bh)=(-2)-(-1)=0FFH,因为结果为负,所以 SF=1。
cmp 操作对象 1, 操作对象 2 指令执行后,SF=1,不能
说明 操作对象 1< 操作对象 2
(ah)=22H,(bh)=0A0H:则 (ah)-(bh)=34-(-96)=130=82H,82H 是 -126 的补码,所以 SF=1。
这里虽然 SF=1,但是并不能说明 (ah)<(bh),因为显然 34>-96。
两个有符号数 A 和 B 相减,得到的是负数,那么可以肯定 A<B,这个思路没有错误;
关键在于我们根据什么来断定得到的是一个负数。
CPU 将 cmp 指令得到的结果记录在 flag 的相关标志位中。
我们可以根据指令执行后,相关标志位的值来判断比较的结果。
单纯的考察 SF 的值不可能知道结果的正负。因为 SF 记录的只是可以在计算机中存放的相应位数的结果的正负。
比如 add ah,al 执行后,SF 记录的是 ah 中 8 位二进制信息所表示的数据的正负。
如果没有发生溢出的话,那么实际结果的正负和逻辑上真正的结果正负就一致了。
所以,我们应该在考察 SF(得知实际结果的正负)的同时考察 OF(得知有没有溢出)就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。
下面,我们以 cmp ah,bh 为例,总结一下 CPU 执行 cmp 结果后,SF 和 OF 的值是如何来说明比较的结果的
- 如果 SF=1,而 OF=0
- OF=0,说明没有溢出,逻辑上真正结果的正负 = 实际结果的正负
- SF=1,实际结果为负,所以逻辑上真正的结果为负,所以 (ah)<(bh)
- 如果 SF=1,而 OF=1
- OF=1,说明有溢出,逻辑上真正结果的正负 ≠ 实际结果的正负
- 简单分析一下,就可以看出,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。这样,SF=1,OF=1,说明了 (ah)>(bh)
- 如果 SF=0,而 OF=1
- OF=1,说明有溢出,逻辑上真正结果的正负 ≠ 实际结果的正负
- 简单分析一下,就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负,这样,SF=0,OF=1,说明了 (ah)<(bh)
- 如果 SF=0,而 OF=0
- OF=0,说明没有溢出,逻辑上真正结果的正负 = 实际结果的正负
- 因 SF=0,实际结果非负,所以逻辑上真正的结果必然非负。所以 (ah)≥(bh)
(博主将以上几点总结为表格形式如下表所示)
SF | 说明 | OF | 说明 | 所以 |
---|---|---|---|---|
0 | 结果非负 | 0 | 没有溢出,逻辑正负 = 实际正负 | (ah)≥(bh) |
0 | 结果非负 | 1 | 有溢出,逻辑正负 ≠ 实际正负 | (ah)<(bh) |
1 | 实际为负 | 0 | 没有溢出,逻辑正负 = 实际正负 | (ah)<(bh) |
1 | 实际为负 | 1 | 有溢出,逻辑正负 ≠ 实际正负 | (ah)>(bh) |
上面,我们深入讨论了 cmp 指令在进行有符号数和无符号数比较时,对 flag 相关标志位的影响,和 CPU 如何通过相关的标志位来表示比较的结果。
在学习中,要注意领会 8086 CPU 这种工作机制的设计思想。实际上,这种设计思想对于各种处理机来说是普遍的。
11.9 检测比较结果的条件转移指令
下面的内容中我们将学习一些根据 cmp 指令的比较结果(即,cmp 指令执行后,相关标志位的值)进行工作的指令。
它们检测的是哪些标志位呢?
就是被 cmp 指令影响的那些,表示比较结果的标志位。
这些条件转移指令通常都和 cmp 相配合使用,就好像 call 和 ret 指令通常相配合使用一样。
因为 cmp 指令可以同时进行两种比较,无符号数和有符号数比较,所以根据 cmp 指令的比较结果进行转移的指令也分为两种,即
- 根据无符号数的比较结果进行转移的条件转移指令,它们检测 ZF、CF 的值
- 根据有符号数的比较结果进行转移的条件转移指令,它们检测 SF、OF 和 ZF 的值
条件转移指令小结
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | ZF=1 |
jne | 不等于则转移 | ZF=0 |
jb | 低于则转移 | CF=1 |
jnb | 不低于则转移 | CF=0 |
ja | 高于则转移 | CF=0,ZF=0 |
jna | 不高于则转移 | CF=1 或 ZF=1 |
这些指令比较常年用,它们都很好记忆,它们的第一个字母都是 j,表示 jump;后面的
- e:表示 equal
- ne:表示 not equal
- b:表示 below
- nb:表示 not below
- a:表示 above
- na:表示 not above
注意观察一下它们所检测的标志位,都是 cmp 指令进行无符号数比较时候,记录比较结果的标志位。
比如 je,检测 ZF 位,当 ZF=1 的时候进行转移,如果在 je 前面使用了 cmp 指令,那么 je 对 ZF 的检测,实际上就是间接地检测 cmp 的比较结果是否为两数相等。