标志寄存器
CPU 内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下 3 种作用。
(1)用来存储相关指令的某些执行结果;
(2)用来为 CPU 执行相关指令提供行为依据;
(3)用来控制 CPU 的相关工作方式。
这种特殊的寄存器在 8086CPU 中,被称为标志寄存器。8086CPU 的标志寄存器有 16 位,其中存储的信息通常被称为程序状态字(PSW)。我们已经使用过 8086CPU 的 ax、bx、cx、dx、si、di、bp、sp、IP、cs、ss、ds、es 等 13 个寄存器了,本章中的标志寄存器(以下称为 flag)使我们要学习的最后一个寄存器。
flag 和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而 flag 寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。

8086CPU 的 flag 寄存器的结构如上图,flag 的 1、3、5、12、13、14、15 位在 8086CPU 中没有使用,不具有任何含义。而 0、2、4、6、7、8、9、10、11 位都具有特殊的含义。
11.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。
注意:在 8086CPU 的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and 等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop 等,它们大都是传送指令。在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的哪些标志位造成影响。
11.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。
11.3 SF 标志
flag 的第 7 位是 SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负数,sf=1;如果非负,sf=0。
计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。比如:
00000001B,可以看作无符号数 1,或有符号数 +1;
10000001B,可以看作为无符号数 129,也可以看作有符号数 -127。
也就是说,对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。比如:
mov al,10000001B
add al,1
结果:(al)=10000001B。
可以将 add 指令进行的运算当作无符号数的运算,那么 add 指令相当于计算 129+1,结果为 130(10000010B);也可以将 add 指令进行的运算当作有符号数的运算,那么 add 指令相当于计算 -127+1,结果为 -126(10000010B)。
不管我们如何看待,CPU 在执行 add 指令的时候,就已经包含了两种含义,也将得到用同一种信息来记录的两种结果。关键在于我们的程序需要哪一种结果。
SF 标志,就是 CPU 对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF 的值则没有意义,虽然相关的指令影响了它的值。
这也就是说,CPU 在执行 add 等指令的时候,是必然要影响到 SF 标志位的值的。至于我们需不需要这种影响,那就看我们如何看待指令所进行的运算了。
11.4 CF 标志
flag 的第 0 位是 CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位的更高位,或从更高位的借位值。
对于位数为 N 的无符号数来说,其对应的二进制信息的最高位,即第 N-1 位,就是它的最高有效位,而假想存在的第 N 位,就是相对于最高有效位的更高位。

我们知道,当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如,下面的指令:
mov al,98h
add al,al ;执行后:(al)=30h,CF=1,CF 记录了从最高有效位向更高有效位的进位置
add al,al ;执行后:(al)=60h,CF=0,CF 记录了从最高有效位向更高有效位的进位置
而当两个数据做减法的时候,有可能向更高位借位。比如,下面的指令:
mov al,97h
sub al,98h ;执行后,(al)=ffh,CF=1,CF 记录了向更高位的借位值。
sub al,al ;执行后,(al)=0,CF=0,CF 记录了向更高位的借位值。
11.5 OF 标志
在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。
那么,什么是机器所能表示的范围呢?
比如说,指令运算的结果用 8 位寄存器或内存单元来存放,对于 8 位的有符号数据,机器所能表示的范围是 -128~127,同理对于 16 位有符号数据,机器所能表示的范围是 -32768~32767。
如果运算结果超出了机器所能表达的范围,将产生溢出。
注意,这里所讲的溢出,只是对有符号数运算而言。含义:我们把运算数当作有符号数的时候,如果超出了最大范围,则把 OF 设置为 1。
一定要注意 CF 和 OF 的区别:CF 是对无符号数运算有意义的标志位,而 OF 是对有符号数运算有意义的标志位。另外需要注意的是,一个数可能既是有符号数,也可以是无符号数。也就是说,运算的时候可能会同时影响 CF 和 OF。而对于我们有没有意义倒说不定。前面我们讲过,CPU 在执行 add 等指令的时候,就包含了两种含义:无符号数运算和有符号数运算;对于有符号数运算,CPU 用 CF 位来记录是否产生了进位;对于无符号数运算,CPU 用 OF 位来记录是否产生了溢出。如下所示:
mov al,0f0h
add al,88h
add 指令执行后:CF=1,OF=1。对于无符号数运算,0f0h+88h 有进位,CF=1;对于有符号数运算,0f0h+88h发生溢出,OF=1。
mov al,0f0h
add al,78h
add 指令执行后:CF=1,OF=0。对于无符号运算,0f0h+78h 有进位,CF=1;对于有符号数运算,0f0h+78h 不发生溢出,OF=0。
我们可以看出,CF 和 OF 所表示的进位和溢出,是分别对无符号数和有符号数运算而言的,它们之间没有任何关系。
11.6 adc 指令
adc 是带进位加法指令,它利用了 CF 位上记录的进位值。
指令格式:adc 操作对象 1, 操作对象 2
功能:操作对象 1 = 操作对象 1 + 操作对象 2 + CF
比如指令 adc ax,bx,实现的功能是: (ax) = (ax) + (bx) + CF
例:
mov ax,2
mov bx,1
sub bx,ax
adc ax,1
执行后,(ax)=4。adc 执行时,相当于计算:(ax) = (ax) + 1 + CF = 2 + 1 + 1 = 4。
为什么要提供这样一条指令呢?
大数的加法可以分为两步来计算:①低位相加;②高位相加再加上低位相加产生的进位值。
看一个例子,计算 1EF000H + 201000H,结果放在ax(高 16 位)和bx(低 16 位)中。
因为两个数据的位数都大于 16,用 add 指令无法进行计算。我们将计算分为两步进行,先将低 16 位相加,然后将高 16 位和进位值相加。程序如下:
mov ax,001EH
mov bx,0F000H
add bx,1000H
adc ax,0020H
adc 指令执行后,也可能产生进位值,所以也会对 CF 位进行设置。由于有这样的功能,我们就可以对任意大的数据进行加法运算。
编写一个子程序,对两个 128 位数据进行相加。
名称:add128
功能:两个 128 位数据进行相加。
参数:ds:si 指向存储第一个数的内存空间,因数据为 128 位,所以需要 8 个字单元,由低地址单元到高地址单元依次存放 128 位由低到高的各个字。运算结果存储在第一个数的存储空间中。
ds:di 指向存储第二个数的内存空间。
程序如下:
add128:
push ax
push cx
push si
push di
sub ax,ax ;将 CF 设置为 0
mov cx,8
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
11.7 sbb 指令
sbb 是带错位减法指令,它利用了 CF 位上记录的借位值。
指令格式:sbb 操作对象1,操作对象2
功能:操作对象1 = 操作对象1 - 操作对象2 - CF
比如指令 sbb ax,bx 实现的功能是:(ax) = (ax) - (bx) -CF
sbb 指令执行后,将对 CF 进行设置。利用 sbb 指令可以对任意大的数据进行减法运算。
sbb 和 adc 是基于同样的思想设计的两条支流,在应用思路上和 adc 类似。
11.8 cmp 指令
cmp 是比较指令,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。
其实,我们通过 cmp 指令执行后,相关标志位的值就可以看出比较的结果。
cmp ax,bx
如果 (ax)=(bx) 则 (ax)-(bx)=0,所以:zf=1;
如果 (ax)≠(bx) 则 (ax)-(bx)≠0,所以:zf=0;
如果 (ax)<(bx) 则 (ax)-(bx) 将产生借位,所以:cf=1,zf=0;
如果 (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,bx 的逻辑含义是比较 ax 和 bx 中的值,如果执行后:
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 为例,总结一下 CPU 执行 cmp 指令后,sf 和 of 的值是如何来说明比较的结果的。
(1) 如果 sf=1,而 of=0
of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;
因 sf=1,实际结果为负,所以逻辑上真正的结果为负,所以 (ax)<(bx)。
(2) 如果 sf=1,而 of=1
of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负
因 sf=1,实际结果为负。
实际结果为负,而又有溢出,这说明是由于溢出导致了实际结果为负,简单分析一下,就可以看出,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。(比如 130 二进制为 1000 0010 B,溢出导致结果为负数,需要注意的是,溢出和进位不一样)
这样,sf=1,of=1,说明了 (ah)>(bh)。
例如:00000000B - 10000000B
(3) 如果 sf=0,而 of=1
of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
因 sf=0,实际结果非负。而 of=1 说明有溢出,则结果非 0,所以,实际结果为正。
实际结果为正,而又有一处,这说明是由于溢出导致了实际结果非负,简单分析一下,就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。(比如 -130 二进制为 0111 1110 B,溢出导致结果为正)
这样,sf=0,of=1,说明了 (ah)<(bh)。
sf=0,of=1,说明最高位相同,并且有溢出,这样说明了 (ax) 必然小于 (bx)。
(4) 如果 sf=0,而 of=0
of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;
因 sf=0,实际结果非负,所以逻辑上真正的结果非负,所以 (ah)≥(bh)。
另外一种理解方式是,其实减法也可以转换为加法,只要把第二个数取反(十进制意义的取反),
比如:
溢出导致结果为正,实际结果必然为负数(cmp 实际上等于两个负数相加)。
mov ah,08Ah
mov bh,070h
cmp ah,bh
结果 sf=0,of=1,因为上面的 (ah)-(bh) 可以看作 1000 1010B + 1001 0000B,这里最高位都为 1,负数相加产生溢出,实际结果为负数,但是低 8 位是正数,所以 sf=0。
另外一个例子:
溢出导致
mov ah,22H
mov bh,0A0H
cmp ah,bh
结果 sf=1,of=1,上面的代码可以看作 0010 0010B + 0110 0000B,这里两个正数相加变负数,也产生了溢出,所以 of=1,sf=1。
溢出导致结果为负数 => 最高位 其实不是符号位,所以我们应该把结果当作正数看待;溢出导致结果为正数 => mov ah,0; add al,bl; 最高位本来是1(负数),但是相加之后最高位进位到更高位,这就超出了负数所能表达的最大范围(-128) ,实际结果为 - 2ˆ8 + 低8位正数。
好像上面的说法都不太好理解,也许可以简单看作,cmp 的时候,把第二个数转换为正数;两个数最高为 1 的时候,为两个负数相加,但是会溢出,结果变正数,但实际结果应该是负数;两个数最高位为 0 的时候,如果产生溢出,并且得到结果是负数,那么说明,超过了数字正数的最大范围(8 位为 127),所以实际结果应该是正数。
11.9 检测比较结果的条件转移指令
“转移”指的是它能够修改 IP,而“条件”指的是它可以根据某种条件,决定是否修改 IP。
比如,jcxz 就是一个条件转移指令,它可以检测 cx 中的数值,如果 (cx)=0,就修改 IP,否则什么也不做。所有条件转移指令的转移位移都是 [-128~127]。
除了 jcxz 之外,CPU 还提供了其他条件转移指令,大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改 IP。它们检测的是哪些标志位呢?就是被 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 的比较结果是否为两数相等。
11.10 DF 标志和串传送指令
flag 的第 10 位是 DF,方向标志位。在串处理指令中,控制每次操作后 si、di 的增减。
df=0 每次操作后 si、di 递增;
df=1 每次操作后 si、di 递减。
我们来看下面的一个串传送指令。
格式:movsb
功能:执行 movsb 指令相当于进行下面几步操作。
(1)((es)*16+(di))=((ds)*16 + (si))
(2)如果 df=0 则:
(si)=(si)+1
(di)=(di)+1
如果 df=1 则:
(si)=(si)-1
(di)=(di)-1
用汇编语法描述 movsb 的功能如下。
mov es:[di], byte ptr ds:[si] ;8086 并不支持这样的指令,这里只是个描述
如果 df=0:
inc si
inc di
如果 df=1:
dec si
dec di
可以看出,movsb 的功能是将 ds:si 指向的内存单元中的字节送入 es:si 中,然后根据标志寄存器 df 位的值,将 si 和 di 递增或递减。
当然,也可以传送一个字,指令如下。
格式:movsw
movsw 的功能是将 ds:si 指向的内存字单元中的字送入 ds:di 中,然后根据标志寄存器 df 位的值,将 si 和 di 递增 2 或递减 2。
用汇编语法描述 movsw 的功能如下。
mov es:[di], word ptr ds:[si] ;8086 并不支持这样的指令,这里只是个描述
如果 df=0:
add si,2
add di,2
如果 df=1:
sub si,2
sub di,2
movsb 和 movsw 进行的是串传送操作中的一个步骤,一般来说,movsb 和 movsw 都和 rep 配合使用,格式如下:
rep movsb
用汇编语法来描述 rep movsb 的功能就是:
s: movsb
loop s
可见,rep 的作用是根据 cx 的值,重复( repeat )执行后面的串传送指令。由于每执行一次 movsb 指令 si 和 di 都会递增或递减指向后一个单元或前一个单元,则 rep movsb 就可以循环实现 (cx) 个字符的传送。
同样的道理,也可以这样使用 movsw:rep movsw。
相当于:
s: movsw
loops
由于 flag 的 df 位决定着串传送指令执行后,si 和 di 改变的方向,所以 CPU 应该提供相应的指令来对 df 位进行设置,从而使程序员能够决定传送的方向。
8086CPU 提供下面两条指令对 df 指令进行设置。
cld 指令:将标志寄存器的 df 位置 0
std 指令:将标志寄存器的 df 位置 1
11.11 pushf 和 popf
pushf 的功能是将标志寄存器的值压栈,而 popf 的功能是从栈中弹出数据,送入标志寄存器中。
pushf 和 popf,为直接访问标志寄存器提供了一种方法。
11.12 标志寄存器在 Debug 中的表示
在 Debug 中,标志寄存器是按照有意义的各个标志位单独表示的。


浙公网安备 33010602011771号