标志寄存器
CPU中有一种特殊的寄存器——标志寄存器(不同CPU中的个数和结构都可能不同),主要有以下三种作用:
- 存储相关指令的某些执行结果
- 为CPU执行相关质量提供行为依据
- 控制CPU相关工作方式
8086CUP中的flag寄存器用来当作标志寄存器,大部分算数或者逻辑运算指令(add/sub/mul/div/inc/dec/or/and)会对标志寄存器有影响。而push/pop等传送指令则没有影响。而且有些指令的执行会影响多个标志位,比如指令sub al,al执行后,ZF、PF、SF等都会受影响。
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
如上图所示,1,3,5,12,13,14,15位没有使用,没有任何意义,而其他几位都有不同的含义。
ZF标志
ZF(Zero Flag)位于flag第6位,零标志位,功能是记录相关指令执行后结果是否为0,如果结果为0,则ZF=1,否则ZF=0。如:
mov ax,1
sub ax,1
执行后结果为0,ZF=1。一般情况下,运算指令(如add,sub,mul,div,inc,or,and)影响标志寄存器,而传送指令(如mov,push,pop)不影响标志寄存器。
PF标志
flag的第2位是PF(Parity Flag)标志位,奇偶标志位,功能是记录相关指令执行后,其结果的所有bit中1的个数是否为偶数,若1的个数是偶数,pf=1,如果是奇数,fp=0。如:
mov al,1
add al,10
SF标志
flag的第7位是SF(Symbol Flag)标志位,符号标志位,它记录相关指令执行后,结果是否为负,如果结果为负,则sf=1,结果为正,sf=0。计算机中通常用补码表示数据,一个数可以看成有符号数或无符号数,如:
00000001B,可以看成无符号1或有符号+1
10000001B,可以看成无符号129或有符号-127
也就是说对于同一个数字,可以当做有符号数运算也可以当做无符号数运算。这里SF位就是表示如果数据按照有符号数运算时,结果是否为负数。如:
mov al,10000001b
add al,1
这段代码结果是(al)=10000010b,可以将add指令进行的运算当做无符号运算,那么相当于129+1=130,也可以当做有符号运算,相当于-127+1=-126。SF标志就是在进行有符号运算的时候记录结果的符号的,当进行无符号运算的时候SF无意义(但还会影响SF,只是对我们来说没有意义了)。
CF标志
flag的第0位是CF(Carry Flag)标志位,进位标志位,一般情况下载进行无符号运算时,他记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。加入一个无符号数据是8位的,也就是0-7个位,那么在做加法的时候就可能造成进位到第8位,这时并不是丢弃这个进位,而是记录在falg的CF位上。如:
mov al,98h
add al,al
执行后al=30h,CF=1。当两个数据做减法的时候有可能向更高位借位,如97h-98h借位后相当于197h-198h,CF也可以用来记录借位,如:
mov al,97h
sub al,98h
OF标志
(Over Flag)在进行有符号运算的时候,如果结果超过了机器能表示的范围称为“溢出”。机器能表示的范围是指如8位寄存器存放或一个内存单元存放,表示范围就是-128~127,16位同理。如果超出了这个范围就叫做溢出,如:
mov al,98
add al,99
mov al,0F0H
add al,088H
第一段代码(al)=(al)+99=98+99=197超过了8位能表示的有符号数的范围,第二段代码结果(al)=(al)+(-120)=(-16)+(-12-)=-136也超过了8位有符号的范围,所以计算的结果是不可信的。如第一段代码计算之后(al)=0C5H,换成补码表示的是-59,98+99=-59很明显是不正确的结果。
flag的第11位是OF标志位,溢出标志位,一般情况下,OF记录有符号数运算结果是否溢出,如果溢出则OF=1,如果没有溢出,OF=0。所以CF是对无符号数的标志,OF是对有符号的标志。但对于一个运算指令,他们是同时生效的,只不过这个指令究竟是有符号还是无符号,是看实际的操作的。有符号CF无意义,无符号OF无意义。
adc指令
adc是带进位加法指令,利用了CF标志位上记录的进位值。格式:adc 操作对象1,操作对象2。功能:操作对象1=操作对象1+操作对象2+CF。如abc ax,bx实现的是(ax)=(ax)+(bx)+CF,如:
mov ax,2
mov bx,1
sub bx,ax
adc ax,1
注意这段代码,首先ax中的值是2,bx中的值是1,然后进行(bx)-(ax)的计算,结果是-1造成了无符号的借位,此时CF=1,在进行adc ax,1时,进行的是(ax)+1+CF=2+1+1=4。仔细分析一下就可以发现,如果把整个加法分开,低位先相加,然后高位相加再加上进位CF, 就是一个完整的加法运算,也就是说add ax,dx这个指令可以拆分为:
add al,bl
adc ah,bh
所以有了adc这个指令我们就可以完成一些更庞大的数据量的加法运算。如计算1EF000H+000H的值:
mov ax,001eh
mov bx,0f000h
add bx,1000h
adc ax,0020h
sbb指令
sbb和adc类似,是带借位的减法,格式:sbb 操作对象1,操作对象2,执行的功能是操作对象1=操作对象1-操作对象2-CF,如:sbb ax,bx即(ax)=(ax)-(bx)-CF。sbb指令影响CF。
cmp指令
cmp是比较指令,cmp的功能相当于减法,只是不保存结果。
cmp obj1,obj2 ; 实际上是执行的 obj1-obj2,结果放在ZF里
cmp执行后影响标志寄存器,其他相关指令通过识别被影响的标志位来得知结果。格式:cmp 操作对象1,操作对象2,执行功能是计算对操作对象1-操作对象2但不保存结果,仅仅根据结果对标志位进行设置,如:cmp ax,ax结果为0,但并不保存在ax中,执行之后zf=1,pf=1,sf=0,cf=0,of=0。若执行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
但实际上往往会出现溢出,如34-(-96)=82H(82H是-126的补码),但应该等于130超出了补码表示的范围,所以sf=1。我们可以同时检验sf和of两个来验证cmp的结果:cmp ah,bh
- 若sf=1,of=0说明没有溢出,那么sf的计算结果正确(ah)<(bh)
- 若sf=1,of=1说明出现了溢出,那么sf结果相反(ah)>(bh)
- 若sf=0,of=1说明有溢出,那么sf结果相反(ah)<(bh)
- 若sf=0,of=0说明没有溢出,那么结果正确(ah)>=(bh)
检测比较结果的条件转移指令
和高级语言的条件判断类似,这类指令会根据特定条件来执行跳转,执行不同的代码。如之前用过的jcxz,就是jump cx zero的缩写,如果cx是0的话就执行跳转。
下面几条指令和cmp一起使用,检测不同的标志位来达到不同的条件跳转效果:
指令 | 含义 | 检测的标志位 |
---|---|---|
je | 等于则转移 | zf=1 |
jne | 不等于转移 | zf=0 |
jb | 小于转移 | cf=1 |
jnb | 不小于转移 | cf=0 |
ja | 大于转移 | cf=0且zf=0 |
jna | 不大于转移 | cf=1或zf=1 |
指令中的字母含义如下:
- e:equa;
- ne:not equal
- b:below
- nb:not below
- a:above
- na:not above
上面的检测都是在cmp进行无符号比较时的检测位,有符号数检测原理一样,只是检测的标志位不同而已。下面看一个例子,如果(ah)=(bh)则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)
cmp ah,bh
je s
add ab,bh
jmp short ok
s:add ah,ah
ok:···
这里注意的是,je检测的是zf位,而不管之前执行的是什么指令,只要zf=1就会发生转移,所以cmp的位置需要仔细的把控,当然是否和cmp配合使用也是取决于编程者,下面例子实现了统计data中数值为8的字节个数,然后用ax保存:
···
data segment
db 8,11,8,1,8,5,63,38
data ends
···
mov ax,data
mov ds,ax
mov bx,0
mov ax,0
mov cx,8
s:cmp byte ptr [bx],8
jne next
inc ax
next:inc bx
loop s
···
DF标志位和串传送指令
flag的第10位是DF(Direction Flag)标志位,方向标志位,在串处理中,每次操作si,di的增减。
- df=0每次操作后si,di递增
- df=1每次操作后si,di递减
串传送指令,movsb,这个指令相当于执行:movsb ;相当于mov es:[di], byte ptr ds:[si]
movsw ;相当于mov es:[di], word ptr ds:[si]
- ((es)16+(di))=((ds)16+(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增减1
同理mobsw就是将DS:SI指向的内存单元中的字送入ES:DI中,然后根据DF的值对SI和DI增减2
但一般来说,movsb和movsw都是和rep联合使用的,格式:rep movsb,这相当于:
s:movsb
loop s
所以rep的作用是根据cx的值重复执行后面的串传送指令,由于每次执行movsb之后si和di都会自行增减,所以使用rep可以完成(cx)个字节的传送。movsw也一样。
由于DF位决定着串传送的方向,所以这里有两条指令用来设置df的值:
cld:df=0
std:df=1
例子:使用串传送指令将data段中第一个字符串复制到他后面的空间中:
···
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
mov ax,data
mov ds,ax
mov si,0
mov es,ax
mov di,16
mov cx,16
cld ;表示clear director,用于把DF变成0
;对应的有std(set director),用于把DF变成1
rep movsb ;rep 命令表示重复movsb,重复的次数放在cx寄存器中
···
pushf和popf
pushf的功能是将标志寄存器的值入栈,popf是出栈标志寄存器。有了这两个命令,就可以直接访问标志寄存器了,如:
mov ax,0
push ax
popf
标志寄存器在Debug中的表示
Debug中-r查看寄存器信息,最后有一段表示,下面列出我们已知的寄存器在Debug里的表示:
标志 | 值1的标记 | 值0的标记 |
---|---|---|
of | OV | NV |
sf | NG | PL |
zf | ZR | NZ |
pf | PE | PO |
cf | CY | NC |
df | DN | UP |