在8086CPU内部的寄存器中,还存在着一种特殊的寄存器,一些指令的执行会触发该寄存器中特定位的值的改变,以用来记录指令执行的一些结果情况。这个寄存器被称为标志寄存器。
标志寄存器也是16位,但不是每一位都有特定的含义的,真正有意义的只有9位,如下:
15 | 14 | 13 | 12 | 11 | 10 | 8 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF | DF | IF | TF | SF | ZF | AF | PF | CF |
每一个标志位,我们可以在 DOSBOX 中通过 debug 查看:
上图中的标志位分别表示如下:
标志 | 值为1 | 值为0 |
---|---|---|
OF | OV(OVERFLOW) | NV(NOT OVERFLOW) |
DF | DN(DOWN) | UP |
IF | EI(ENABLE INTERRUPT) | DI(DIABLE INTERRUPT) |
SF | NG(NEGATIVE SIGN) | PL (PLUS SIGN) |
ZF | ZR(ZERO) | NZ(NOT ZERO) |
AF | AC( AUXILIARY CARRY) | NA(NOT AUXILIARYCARRY) |
PF | PE(PARITY EVEN) | PO(PARITY ODD) |
CF | CY(CARRY) | NC(NOT CARRY) |
下面介绍部分标志位,大家可以在dosbox中根据上述的值来进行验证。
ZF 标志
标志寄存器的第 6 位是 ZF,零标志位。该标志位记录了相关指令的执行结果是不是为 0,若结果为 0,则 ZF=1,否则,ZF=0。
如:
mov ax, 1
sub ax, ax
指令执行后,结果为 0 ,则 ZF=1。
mov ax, 2
sub ax, 1
指令执行后,结果不为 0 , 则 ZF=0。
有些指令的执行,即使结果为 0 ,也不会影响 ZF 标志位的:
mov ax, 0
像 add、sub、mul、div、inc、or、and 等运算指令都会影响标志寄存器,而有些指令则对标志寄存器没有影响,如mov、push、pop等传送指令。
PF 标志
标志寄存器的第 2 位是 PF,奇偶标志位,该标志位记录了相关指令执行后,其结果中的所有 bit 位中的 1 的个数是否为偶数。若结果是偶数,则 PF=1,否则,PF=0。
mov al, 1
and al, 0
指令执行后,结果为00000000B,有 0 个 1,为偶数,则 PF = 1。
mov al, 1
or al, 1
指令执行后,结果为00000001B,有 1 个 1,为奇数,则 PF=0。
SF 标志
标志寄存器的第 7 位是SF,符号标志位,该标志位记录了相关指令执行后,结果是否为负。如果结果为负,则 SF=1,否则,SF=0。
对于计算机中的数值,二进制来说,即可以当成有符号数,最高位代表正负,也可以当成无符号数来看待。所以,计算机在执行add等指令时,也就相当于具有了两种含义,关键在于我们需要哪一种结果。
SF 标志,就是CPU对有符号数运算结果的一种记录,在我们进行有符号数运算时,可以通过该标志位来得知结果的正负。同时,若我们当成无符号数运算时,虽然结果同样会改变 SF 的值,但该值对于无符号来说是没有意义的。
如:
mov al, 10000001B
add al, 1
指令执行后,结果为 10000010B,SF=1,表示:如果指令进行的是有符号运算,则结果为负。
CF 标志
标志寄存器中的第 0 位是 CF,进位标志位。该标志位是针对的无符号数运算,其记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
如:
mov al, 98H
add al, al
该指令执行后,al=30H,CF=1,该结果向更高位进位1,所以,CF 记录了从最高有效位到更高位的进位值。
mov al, 30h
add al, al
指令执行后,al=60H,CF=0,该结果向更高位进位0,所以,CF 记录了从最高有效位到更高位的进位值。
mov al, 00000010B
mov bl, 00000001B
sub bl, al
指令执行后,bl=11111111B,CF=1,该结果向更高位借位1,所以,CF记录了从最高有效位到更高位的借位值。
通过CF记录的进位或借位值,我们可以结合指令 adc 和 sbb 来计算更多位数的加减法:
如:
计算1FFFFH + 1的值,BX 存放高位,AX 存放低位:
mov ax, 0FFFFH
mov bx, 0001H
add ax, 1 ;inc不会影响CF的值,add结果会产生进位值1
adc bx, 0 ;adc表示 运算结果 + CF的值
结果为:BX=2,AX=0。即结果为20000H。
如:
计算10000H - 1的值,BX 存放高位,AX 存放低位:
mov ax, 0
mov bx, 1
sub ax, 1
sbb bx, 0 ;sbb表示 运算结果 - CF的值
结果为:BX=0,AX=FFFFH,即结果为:FFFFH。
OF 标志
标志寄存器的第 11 位是OF,溢出标志位,该标志位记录了有符号数运算时的结果是否超出了所能表示的范围,如果超出,则, OF=1,否则,OF=0。
注意,该标志位和 CF 标志位的区别,CF 是针对无符号数运算时记录的标志,OF 是针对有符号数运算时记录的标志。
如:
mov al, 98
add al, 99
指令执行后,CF=0,OF=1。
CPU 执行 add等指令时,就包含了两种含义:无符号数运算和有符号数运算。
对于无符号数运算,99 + 98 没有产生进位,所以 CF=0。
对于有符号数运算,99 + 98 = 197,超出了8位寄存器所能保存的有符号数范围(-128~127),发生了溢出,所以 OF=1,同时,因为 SF 也会记录结果的正负,所以SF=1。
所以,对于有符号数运算,若发生溢出,结果则是不正确的。
DF 标志
标志寄存器的第 10 位是DF,方向标志位。该标志位在串处理指令中,控制每次操作后寄存器 SI 和 DI 的增减。
若 DF=0,每次操作后,SI 和 DI 递增。
若 DF=1,每次操作后,SI 和 DI 递减。
该标志位通过以下指令来设置:
cld ;置 DF = 0
std ;置 DF = 1
该标志位和串处理指令配合使用:
movsb
该串指令把 DS:[SI] 内存单元的字节型数据传送给 ES:[DI] 指向的内存单元中。
该指令执行完后,会根据 DF 的值对 SI 和 DI 进行相应的增减1.
movsw
该串指令和 movsb 指令功能一致, 只是操控的数据是字型数据,每次操作后,根据 DF 的值对 SI 和 DI 进行相应的增减2。
一般情况下,该串指令常和 rep 配合使用:
rep movsb
或
rep movsw
该指令相当于进行了以下操作:
s:
movsb 或 movsw
loop s
所以, rep 指令的作用是根据 CX 的值,重复执行后面的串传送指令。相当于实现了 CX 个字节或字型数据的传送。
如:
assume cs:code, ds:data
data segment
db 'Welcome to masm!'
db 16 dup (0) ;把Welcome to masm!写入到该处
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov es, ax
mov di, 16
mov cx, 16 ;设置循环的次数,即要传送的字节型数据的数量
cld ;设置 DF=0
rep movsb ;循环把 DS:[SI] 处的数据传送到 ES:[DI] 处
;程序返回退出
mov ax, 4c00h
int 21h
code ends
end start
对于标志寄存器,有些指令可以影响标志位,有些不可以,有些可以影响多个标志位,有些可以影响单个标志位。根据这些标志位,我们可以做很多有实际用途的功能。
CMP 指令
cmp 指令是比较指令,有两个操作对象,指令执行相当于做了减法,只是不保存结果,但会影响标志寄存器中相应的多个标志位。
cmp 操作对象a, 操作对象b
如:
mov ax, 8
mov bx, 3
cmp ax, bx ;相当于ax - bx,但结果不会改变ax,bx的值
指令执行后,ax = 8,所以cmp指令不影响操作对象的值,但会影响标志位:
ZF=0, PF=1, SF=0, CF=0, OF=0
通过该指令和相应的标志位,我们可以知道相应的比较结果,比较的结果一般有如下几种:
a = b
a <> b
a > b
a >= b
a < b
a <= b
对于无符号数的比较,根据以上结果情况,我们只需要分析以下标志位即可:
ZF=0,结果不为0,说明 a <> b
ZF=1,结果位0,说明 a = b
CF=1, 产生了借位值,说明 a < b
CF=0, 无借位,说明 a >= b
CF=0 且 ZF=0,无借位且结果不为0,说明 a > b
CF=1 或 ZF=1,有借位或结果为0,说明 a <= 0
对于有符号数的比较,需要关注的标志位有所不同,因为有符号数运算会影响符号标志位 SF 和溢出标志位 OF,所以除了CF不在关注外,还需要关注SF和OF标志,同时不能仅根据 SF 的正负来判断两个操作数的大小,需要配合使用来判断:
1、SF=1,OF=0
说明结果没有溢出,即结果是正确的,同时结果为负,则 a < b
2、SF=1,OF=1
说明结果有溢出,同时结果又为负,说明是溢出导致的结果为负,所以实际的结果其实是正,则 a > b
3、SF=0,OF=1
说明结果有溢出,同时结果又为正,说明是溢出导致的结果为正,所以实际的结果其实是负,则 a < b
4、SF=0,OF=0
说明结果没有溢出,同时结果为正,则 a >= b
通过以上分析,指令 CMP 可以判断两个数的比较结果,并影响相应的标志位。同时8086也提供了大量的条件转移指令来根据这些标志位进行转移:
对于无符号数来说,常用的转移指令有:
je 标号 ;等于则转移至标号处,根据标志位 ZF=1
jne 标号 ;不等于则转移至标号处,根据标志位 ZF=0
jb 标号 ;小于则转移至标号处,根据标志位 CF=1
jnb 标号 ;大于等于则转移至标号处,根据标志位 CF=0
ja 标号 ;大于则转移至标号处,根据标志位CF=0且ZF=0
jna 标号 ;小于等于则转移至标号处,根据标志位 CF=1 或 ZF=1
cmp指令常和条件转移指令结合使用,来实现类似 if~else 的功能:
如,实现查找以下数据中大于 3 的个数,保存到 ax 中:
assume cs:code
data segment
db 1, 2, 3, 4, 5, 1, 2, 3, 3, 4, 7
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx, 0
mov cx, 11
mov ax, 0
s:
cmp byte ptr ds:[bx], 3
jna next
inc ax
next:
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end start
条件转移指令都是短转移指令,根据标号处的偏移地址的位移来转移的,所以,转移范围为:-128~127
更多条件转移指令可参考如下,注意有些指令的功能是一致的:
指令格式 | 判断标志位 | 转移说明 |
---|---|---|
JZ | ZF=1 | 等于0或相等则转移 |
JE | ZF=1 | 等于0或相等则转移 |
JNZ | ZF=0 | 不等于0或不相等则转移 |
JNE | ZF=0 | 不等于0或不相等则转移 |
JS | SF=1 | 为负则转移 |
JNS | SF=0 | 为正则转移 |
JO | OF=1 | 溢出则转移 |
JNO | OF=0 | 无溢出则转移 |
JP | PF=1 | 偶则转移 |
JPE | PF=1 | 偶则转移 |
JNP | PF=0 | 奇则转移 |
JPO | PF=0 | 奇则转移 |
JB | CF=1 | 小于或进位标志被置1则转移(无符号数) |
JNAE | CF=1 | 小于或进位标志被置1则转移(无符号数) |
JC | CF=1 | 小于或进位标志被置1则转移(无符号数) |
JNB | CF=0 | 不小于或大于等于或进位标志被置0则转移(无符号数) |
JAE | CF=0 | 不小于或大于等于或进位标志被置0则转移(无符号数) |
JNC | CF=0 | 不小于或大于等于或进位标志被置0则转移(无符号数) |
JBE | (CF或ZF)=1 | 小于等于则转移(无符号数) |
JNA | (CF或ZF)=1 | 小于等于则转移(无符号数) |
JNBE | (CF或ZF)=0 | 大于则转移(无符号数) |
JA | (CF或ZF)=0 | 大于则转移(无符号数) |
JL | (SF异或OF)=1 | 小于则转移(有符号数) |
JNGE | (SF异或OF)=1 | 小于则转移(有符号数) |
JNL | (SF异或OF)=0 | 大于等于则转移(有符号数) |
JGE | (SF异或OF)=0 | 大于等于则转移(有符号数) |
JLE | ((SF异或OF)或ZF)=1 | 小于等于则转移(有符号数) |
JNG | ((SF异或OF)或ZF)=1 | 小于等于则转移(有符号数) |
JNLE | ((SF异或OF)或ZF)=0 | 大于则转移(有符号数) |
JG | ((SF异或OF)或ZF)=0 | 大于则转移(有符号数) |
PUSHF 和 POPF
该指令为把标志寄存器中的值压栈和把栈中的数据转移至标志寄存器中,通过该指令我们可以对标志寄存器进行保存和恢复,同时还能对标志位进行相应更改:
如:
所有标志位都置0
pushf
pop ax
and ax, 0
push ax
popf