汇编语言 —— 标志寄存器
CPU 内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下 3 种作用:
- 用来存储相关指令的某些执行结果;
- 用来为 CPU 执行相关指令提供行为依据;
- 用来控制 CPU 的相关工作方式。
8086CPU 的标志寄存器结构如下图:

1 ZF 标志
flag 的第 6 位是 ZF,零标志位。它记录相关指令执行后,其结果是否为 0。如果结果为 0,那么 ZF=1;如果结果不为 0,那么 ZF=0。
指令:
mov ax, 1
sub ax, 1
执行后,结果为 0,则 ZF=1。
指令:
mov ax, 2
sub ax, 1
执行后,结果不为 0,则 ZF=0。
2 PF 标志
Flag 的第 2 位是 PF,奇偶标志位。它记录相关指令执行后,其结果的所有 bit 位中 1 的个数是否为偶数。如果 1 的个数为偶数,PF=1,如果为奇数,那么 PF=0。
指令:
mov al, 1
add al, 10
执行后,结果为 00001011B,其中有 3 个 1,则 PF=0。
指令:
mov al, 1
or al, 2
执行后,结果为 00000011B,其中有 2 个 1,则 PF=1。
3 SF 标志
Flag 的第 7 位是 SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,SF=1;如果非负,SF=0。
SF 标志,是 CPU 对有符号数运算结果的一种记录,它记录数据的正负。如果我们将数据当作无符号数来运算,SF 的值则没有意义,虽然相关的指令影响了它的值。
指令:
mov al, 10000001B
add al, 1
执行后,结果为 10000010B,SF=1,表示:如果指令进行的是有符号数运算,那么结果为负。
指令:
mov al, 10000001B
add al, 01111111B
执行后,结果为 0,SF=0,表示:如果指令进行的是有符号数运算,那么结果为非负。
4 CF 标志
Flag 的第 0 位是 CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
对于位数为 N 的无符号数来说,其对应的二进制信息的最高位,即第 N-1 位,就是它的最高有效位,而假想存在的第 N 位,就是相对于最高有效位的更高位。

当两个数据相加的时候, 有可能产生从最高有效位向更高位的进位。比如,两个 8 位数据:98H+98H,将产生进位。由于这个进位值在 8 位数中无法保存,CPU 在运算的时候记录在一个特殊的寄存器的某一位上,8086CPU 就用 Flag 的 CF 位来记录这个进位值。
指令:
mov al, 98H
add al, al ; 执行后: (al)=30H,CF=1,CF 记录了从最高有效位向更高位的进位值
add al, al ; 执行后: (al)=60H,CF=0,CF 记录了从最高有效位向更高位的进位值
当两个数据做减法的时候,有可能向更高位借位。比如,两个 8 位数据:97H-98H,将产生借位,借位后,相当于计算 197H-98H,Flag 的 CF 位也可以用来记录这个借位值。
指令:
mov al, 97H
sub al, 98H ; 执行后: (al)=FFH,CF=1,CF 记录了向更高位的借位值
sub al, al ; 执行后: (al)=0,CF=0,CF 记录了向更高位的借位值
5 OF 标志
在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。
Flag 的第 11 位是 OF,溢出标志位。 一般情况下,OF 记录了有符号数运算的结果是否发生了溢出。如果发生溢出,OF=1;如果没有,OF=0。
一定要注意 CF 和 OF 的区别:CF 是对无符号数运算有意义的标志位,而 OF 是对有符号数运算有意义的标志位。
指令:
mov al, 98
add al, 99
add 指令执行后:CF=0,OF=1。
CPU 在执行 add 等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。对于无符号数运算,CPU 用 CF 位来记录是否产生了进位;对于有符号数运算,CPU 用 OF 位来记录是否产生了溢出。
6 ADC 指令
adc 是带进位加法指令,它利用了 CF 位上记录的进位值。
比如指令adc ax, bx
实现的功能是:(ax)=(ax)+(bx)+CF。
CPU 提供 adc 指令的目的,是来进行加法的第二步运算的。adc 指令和 add 指令相配合就可以对更大的数据进行加法运算。
在执行 adc 指令的时候加上的 CF 的值的含义,是由 adc 指令前面的指令决定的,也就是说,关键在于所加上的 CF 值是被什么指令设置的。显然,如果 CF 的值是被 sub 指令设置的,那么它的含义就是借位值;如果是被 add 指令设置的,那么它的含义就是进位值。
编程,计算 1EF0001000FH+2010001EF0H,结果放在 ax(高16位),bx(次高16位),cx(低16位)中。
计算分 3 步进行:
- 先将低 16 位相加,完成后,CF 中记录本次相加的进位值;
- 再将次高 16 位和 CF(来自低 16 位的进位值)相加,完成后,CF 中记录本次相加的进位值;
- 最后高 16 位和 CF(来自次高 16 位的进位值)相加,完成后,CF 中记录本次相加的进位值。
程序如下:
mov ax, 001EH
mov bx, 0F000H
mov cx, 1000H
add cx, 1EF0H
adc bx, 1000H
adc ax, 0020H
7 SBB 指令
sbb 是带借位减法指令,它利用了 CF 位上记录的借位值。
比如指令sbb ax, bx
实现的功能是:(ax)=(ax)-(bx)-CF。
sbb 指令执行后,将对 CF 进行设置。利用 sbb 指令可以对任意大的数据进行减法运算。
计算 003E1000H-00202000H,结果放在 ax,bx 中。
程序如下:
mov bx, 1000H
mov ax, 003ЕН
sub bx, 2000H
sbb ax, 0020H
8 CMP 指令
cmp 是比较指令,cmp 的功能相当于减法指令,只是不保存结果。cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
比如,指令cmp ax, ax
,做 (ax)-(ax) 的运算,结果为 0,但并不在 ax 中保存,仅影响 flag 的相关各位。指令执行后:ZF=1, PF=1, SF=0, CF=0, OF=0。
CMP 指令——无符号数运算
我们通过 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。
CPU 在执行 cmp 指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算。所以利用 cmp 指令可以对无符号数进行比较,也可以对有符号数进行比较。
CMP 指令——有符号数运算
我们通过 cmp 指令执行后,考查 SF (得知实际结果的正负)的同时考查 OF (得知有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。以 cmp ah, bh
为例:
-
如果 SF=1,OF=0
OF=0,说明没有溢出,逻辑上真正结果的正负 == 实际结果的正负; 因 SF=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。
-
如果 SF=1,OF=1
OF=1,说明有溢出,逻辑上真正结果的正负 != 实际结果的正负; 因 SF=1,实际结果为负。 实际结果为负,而又有溢出,这说明是由于溢出导致了实际结果为负,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。 因此,SF=1,OF=1,说明 (ah)=(bh)。
-
如果 SF=0,OF=1
OF=1,说明有溢出,逻辑上真正结果的正负 != 实际结果的正负; 因 SF=0,实际结果非负。而 OF=1 说明有溢出,则结果非 0,所以,实际结果为正。 实际结果为正,而又有溢出,这说明是由于溢出导致了实际结果非负,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。 因此,SF=0,OF=1,说明 (ah)<(bh)。
-
如果 SF=0,而 OF=0
OF=0,说明没有溢出,逻辑上真正结果的正负 = 实际结果的正负; 因 SF=0,实际结果非负,所以逻辑上真正的结果非负,所以 (ah)≥(bh)。
9 检测比较结果的条件转移指令
除了 jcxz 之外,CPU 还提供了其他条件转移指令,大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改 IP。检测的标志位就是被 cmp 指令影响的那些,表示比较结果的标志位。这些条件转移指令通常都和 cmp 相配合使用。
因为 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 |
10 DF 标志和串传送指令
Flag 的第 10 位是 DF,方向标志位。在串处理指令中,控制每次操作后 si、di 的增减:
- DF=0 每次操作后 si、di 递增;
- DF=0 每次操作后 si、di 递减。
10.1 movsb 和 movsw 指令
功能:执行 movsb 指令相当于进行下面几步操作:
- ((es)x16+(di))=((ds)x16+(si))
- 如果 DF=0 则:
- (si)=(si)+1
- (di)=(di)+1
- 如果 DF=1 则:
- (si)=(si)-1
- (di)=(di)-1
可以看出,movsb 的功能是将 ds:si 指向的内存单元中的字节送入 es:di 中,然后根据标志寄存器 DF 位的值,将 si 和 di 递增或递减。
当然,也可以传送一个字,使用 movsw 指令。
movsw 的功能是将 ds:si 指向的内存字单元中的字送入 es:di 中,然后根据标志寄存器 df 位的值,将 si 和 di 递增 2 或递减 2。
10.2 rep 指令
movsb 和 movsw 进行的是串传送操作中的一个步骤,一般来说,movsb 和 movsw 都和 rep 配合使用,格式为:rep movsb
或 rep movsw
。
rep 的作用是根据 cx 的值,重复执行后面的串传送指令。由于每执行一次 movsb 指令 si 和 di 都会递增或递减指向后一个单元或前一个单元,则 rep movsb
就可以循环实现(cx)个字符的传送。
10.3 cld 和 std 指令
由于 flag 的 df 位决定着串传送指令执行后,si 和 di 改变的方向,所以 CPU 应该提供 相应的指令来对 df 位进行设置,从而使程序员能够决定传送的方向。
8086CPU 提供下面两条指令对 df 位进行设置:
- cld 指令:将标志寄存器的 df 位置 0;
- std 指令:将标志寄存器的 df 位置 1。
10.4 串传送指令案例
例题:编程,用串传送指令,将 data 段中的第一个字符串复制到它后面的空间中。
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
我们分析一下,使用串传送指令进行数据的传送,需要给它提供一些必要的信息,它们是:
- 传送的原始位置:ds:si;
- 传送的目的位置:es:di;
- 传送的长度:cx;
- 传送的方向:df。
在这个问题中,这些信息如下:
- 传送的原始位置:data:0;
- 传送的目的位置:data:0010;
- 传送的长度: 16;
- 传送的方向:因为正向传送(每次串传送指令执行后,si 和 di 递增)比较方便,所以设置 DF=0。
明确上述信息后,编写程序:
mov ax, data
mov ds, ax
mov si, 0 ; ds:si 指向 data:0
mov es, ax
mov di, 16 ; es:di 指向 data:0010
mov cx, 16 ; (cx)=16,rep 循环 16 次
cld ; 设置 DF=0,正向传送
rep movsb
11 PUSHF 和 POPF
pushf 的功能是将标志寄存器的值压栈,而 popf 是从栈中弹出数据,送入标志寄存器中。pushf 和 popf,为直接访问标志寄存器提供了一种方法。