标志寄存器
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。
(1) 用来存储相关指令的某些执行结果;
(2) 用来为CPU执行相关指令提供行为依据;
(3) 用来控制CPU的相关工作方式。
8086CPU中的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW),标志寄存器以下简称为flag。标志位如图:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF
flag的1,3,5,12,13,14,15位在8086CPU中没有使用,不具有任何含义。而0,2,4,6,7,8,9,10,11位都具有特殊的含义。
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
在计算机中1表示逻辑真,0表示逻辑假,所以,zf = 1结果为0,zf = 0结果不为0.
注意,在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如, add, sub, mul, div, inc, or, and等,他们大都是运算指令(进行逻辑或算术运算):有的指令的执行对标志寄存器没有影响,比如,mov, push, pop等,他们大都是传送指令。在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的那些标志位造成影响。
PF标志
flag的第2位是PF,奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如果1的个数为偶数,pf=1,如果为奇数,那么pf = 0.
比如,指令:
mov al,1
add al,10
执行后结果为0000 1011B,其中3(奇数)个1,则pf = 0;
mov al, 1
or al, 2
执行后,结果为 0000 0011B,其结果有2(偶数)个1,则pf = 1;
SF标志
flag的第7位是SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,sf = 1;如果结果非负,sf = 0;
SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关执行影响了它的值。
这也就说,CPU在执行add等指令时,时必然要影响到SF标志位的值的。至于我们需不需要这种影响,
那就看我们如何看待指令所进行的运算了。
比如:
mov al,1000 0001B
add al,1
执行后,结果为1000 0010B,sf = 1,表示:如果指令进行的是有符号数运算,那么结果为负;
mov al,1000 0001B
add al, 0111 1111B
执行后,结果为0,sf = 0,表示:如果指令进行的是有符号数运算,那么结果为非负。
CF标志
flag的第0位是CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向跟高位的进位值,或者从跟高位的借位值。
比如:
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记录了向更高位的借位值
当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如,两个8位数据:98H+98H,将产生进位。由于这个进位值在8位数中无法保存。其实CPU在运算的时候,并不丢弃这个进值位,而是记录在一个特殊的寄存器的某一位上。8086CPU就用flag的CF位来记录这个进位值。
OF标志
flag的第11位是OF,溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。如果发生了溢出,OF = 1,如果没有,OF = 0。
这里的溢出只针对有符号数
比如:
mov al, 98
add al, 99
执行后将产生溢出。因为进行有符号数运算是:
(al) = (al) + 99= 99 + 98 = 197 注意:这里是十进制的数。
197超出了机器所能表示的8位有符号数的范围: -128~127
add执行后:CF = 0,OF= 1,即他没有产生进位,但发生了溢出。进位对于无符号数,溢出对于有符号数。
所以对于机器码进行add或者sub等都会影响OF,CF。至于要使用那种影响要看如何看待这段机器码。
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) + 1 + CF = 2 + 1 + 1 = 4.
看一下CF的含义。在执行adc指令的时候加上的CF的值的含义,是由adc指令前面的指令决定的,也就是说,关键在于加上的CF值是被什么指令设置的。显然,如果CF的值是被sub指令设置的,那么它的含义就是错位值;如果是被add指令设置的,那么它的含义就是进位值。我们来看一下两个数据:0198H和0183H如何相加的:
01 98
01 83
+
03 1B
可以看出,加法可以分两步进行:1,低位相加;2高位相加再加上低位相加产生的进位值。
下面的指令和adc ax, bx 具有相同的结果:
add al, bl
adc ah,bh
看来CPU提供adc指令的目的,就是来进行加法的第二步运算的。adc指令和add指令相配合就可以对更大的数据进行加法运算。我们来举一个例子:
编程计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。
因为两个数据的位数都大于16,用add指令无法进行计算。我们将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。程序如下。
mov ax , 001EH
mov bx, 0F000H
add bx, 1000H
adc ax, 0020H
adc指令执行后,也可能产生进位值,所以也会对CF位进行设置。由于有这样的功能,我们就可以对任意大的数据进行加法运算。
例如:
编程,计算 1E F000 1000H + 20 1000 1EF0H, 结果放在ax(最高16位),bx(次高16位),cx(低位16位)中。
计算分3步进行:
(1)先将低16位相加, 完成后,CF中记录本次相加的进位值;
(2)再将次高16位和CF(来自低16位的进位制)相加,完成后,CF中记录本次相加的进位值;
(3)最后高16位和CF(来自次高16位的进位值)相加,完成后,CF中记录本次相加的进位值。
程序如下。
mov ax, 001EH
mov bx, 0F000H
mov cx, 1000H
add cx, 1EF0H
adc bx, 1000H
adc ax 0020H
sbb指令
sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sub操作对象1,操作对象2
功能:操作对象1 = 操作对象1 - 操作对象2 - CF
比如指令 sbb ax, bx 实现的功能是: (ax) = (ax) - (bc) - CF
sbb指令执行后,将对CF进行设置。利用sbb指令可以对任意大的数据进行减法运算。比如,计算003E1000H - 00202000H,结果放在ax,bx中,程序如下:
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H
sbb和adc是基于同样的思想设计的两条指令,在应用思路上和adc类似。
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.
下面指令:
mov ax, 8
mov bx,3
执行后:(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)将产生借位,所以:zf = 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, 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)
有符号数比较
cmo ah, bh
zf = 1,说明(ah)=(bh)
zf = 0,说明(ah) (bh)
sf = 1&& of = 0 (ah) < (bh)
// of = 0,说明没有溢出,逻辑上真正结果正负=实际结果正负;
sf = 1, 而of = 1 (ah) > (bh)
//of = 1, 说明有溢出,如果因为溢出导致了结果为负,那么逻辑上真正的结果必然为正
sf = 0, of = 1 (ah) < (bh)
//of = 1, 说明有溢出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。
sf = 0, of = 0 (ah)≥(bh)
单纯地考查sf的值不可能知道结果的正负。因为sf记录的是ah中的8位二进制信息所表示的数据的正负。cmp, ah, bh执行后,sf记录的是(ah) - (bh)所得到的8位结果数据的正负。
虽然这个结果没有在我们能够使用的寄存器或内存单元中保存,但是在指令执行的过程中,它暂存在CPU内部的暂存器中。
所得到的相应结果的正负,并不能说明,运算所应该得到的结果的正负。这是因为在运算的过程中可能发生溢出。如果有这种情况发生,那么,sf的值就不能说明任何问题。比如:
mov ah, 22H
mov bh, 0A0H
sub ah, bh
结果 sf = 1,运算实际得到的结果是(ah) = 82H,但是在逻辑上,运算所应该得到的结果是:34 -(-96) = 130. 就是因为130这个结果作为一个有符号数超出了-128~127这个范围,在ah中不能表示,而ah中的结果被CPU当作有符号数解释位-126。而sf被用来记录这个实际结果的正负,所以sf = 1。但是sf = 1不能说明在逻辑上,运算所得到的正确结果的正负。
又比如:
mov ah 08AH
mov bh,070H
cmp ah,bh
结果 sf = 0,运算(ah) - (bh)实际得到的结果是1AH,但是在逻辑上,运算所应该得到的结果是:(-118) - 112 = -230. sf记录实际结果的正负,所以sf = 0.但sf = 0不能说明在逻辑上,运算所得到的正确结果。
所以。我们应该在考察sf(得知实际结果的正负)的同时考查of(得知有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。
检测比较结果的条件转移指令
因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种,即根据无符号数的比较结果进行转移的条件转移指令(他们检测zf,cf的值)和根据有符号数的比较结果进行转移的条件转移指令(他们检测sf,of和zf的值)
下面是常用的根据无符号数的比较结果进行转移的条件转移指令
指令 含义 检测的相关标志位
jcxz cx值为0则转移 cx = 0
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
比如:
编程,统计data段中数值为8的字节的个数,用ax保存统计结果
data段的8个字节如下:
data segment
db 8, 11, 8, 1, 8, 5, 63,38
data ends
程序如下:
mov ax, data
mov ds, ax
mov bx, 0 ;ds:bx指向第一个字节
mov ax, 0 ;初始化累加器
mov cx , 8
s: cmp byte ptr [bx], 8 ;和8进行比较
jne next ;如果不相等转到next。继续循环
inc ax ;如果相等就将计数值+1
next: inc bx
loop s ;程序执行后:(ax) = 3
这个程序也可以这样写:
mov ax, data
mov ds, ax
mov bx, 0 ;ds:bx指向第一个字节
mov ax, 0 ;初始化累加器
mov cx , 8
s: cmp byte ptr [bx], 8 ;和8进行比较
je ok ;如果相等转到ok
jmp short next ;如果不相等就转到next。继续循环
ok:inc ax ;如果相等就将计数值+1
next:inc bx
loop s
比起第一个程序,他直接遵循了“等于8则计数+1”的原则,用je指令检测等于8的情况,但是没有第一个程序精简。第一个程序用jne检测不等于8的情况,从而间接检测等于8的情况。要注意在使用cmp和条件转移指令时的这种编程思想。
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的功能是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减
格式:movsw
movsw的功能是将ds:si指向的内存单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用,格式如下:
rep movsb
用汇编语法来描述rep movsb的功能就是:
s:movsb
loop s
可见,rep的作用是根据cx的值,重复执行后面的串传送指令。由于每执行一次movsb指令si和di都会递增或递减指向后一个单元或前一个单元,则rep movsb就可以循环实现(cx)个字符的传送。
由于flag的df位决定着串传送指令执行后,si和di改变的方向,所以CPU应该提供相应的指令来对df位指令进行设置,从而是程序员能够决定传送的方向。
8086CPU提供下面两条执行对df位进行设置。
cld指令:将标志寄存器的df位置0
std指令:将标志寄存器的df位置1
编程,用串传送指令,将data段中的第一个字符串复制到它后面的空间中。
data segment
db 'Welcome to masm!'
db 16 dup(0)
data ends
我们分析一下一下,使用串传送指令进行数据的传送,需要给它提供一些必要的信息,它们是:
1 传送的原始位置:ds:si;
2 传送的目的位置:es:di;
3 传送的长度:cx;
4 传送的方向:df;
在这个问题中,这些信息如下。
1 传送的原始位置:data:0;
2 传送的目的位置:data:0010;
3 传送的长度:16;
4 传送的方向:因为正方向传送(每次串传送指令执行后,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
pushf 和 popf
pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。
pushf和popf,为直接访问标志寄存器提供了一种方法。
标志寄存器在Debug中的表示
在Debug中,标志寄存器是按照有意义的各个标志位单独表示的。在Debug中,我们可以看到下面的信息。

下面列出Debug对我们已知的标志位的表示。
标志 值为1的标记 值为0的标记
of OV NV
sf NG PL
zf ZR NZ
pf PE PO
cf CY NC
df DN UP