标志寄存器
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有三种作用:
1) 用来存储相关指令的某些执行结果;
2) 用来为CPU执行相关指令提供行为依据;
3) 用来控制CPU的相关工作方式。
这种特殊的寄存器在8086CPU中,被称为标志寄存器。8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)。简称flag。
flag和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。
而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中没有使用,不具有任何含义,而其余位都具有特殊的含义。
ZF标志
flag的第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0。如果结果为0,那么ZF=1,如果结果不为0,那么ZF=0。
mov ax,1
sub ax,1
执行后,结果为0,则ZF=1,表示“结果是0”。
mov ax,2
sub ax,1
执行后,结果不为0,则ZF=0,表示“结果不是0”。
在计算机中0表示逻辑假,表示否定,1表示逻辑真,表示肯定。
注意,在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如:add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或自述运算);有的指令的执行对标志寄存器没有影响,比如:mov、push、pop等,它们大都是传送指令。
我们在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标记寄存器的哪些标志位造成影响。
PF标志
flag的第2位是PF,奇偶标志位。它记录相关指令执行后,其结果的所有二进制位中1的个数是否为偶数。如果1的个数为偶数,PF=1,如果为奇数,那么PF=0。
比如:
mov al,1
add al,10
执行后,结果为00001011B,其中有3(奇数)个1,则PF=0。
SF标志
flag的第7位是SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,SF=1,如果非负,SF=0。
我们知道计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。比如:
00000001B,可以看作为无符号数1,或有符号数+1;
10000001B,可以看作为无符号数129,也可以看作有符号数-127。
这也就是说,对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。比如:
mov al,10000001B
add al,1
结果:(al)=10000010B
我们可以将add指令进行的运算当作无符号数的运算,那么add指令相当于计算129+1,结果为130(10000010B);也可以将add指令进行的运算当作是有符号数的运算,那么add指令相当于计算-127+1,结果为-126(10000010B)。
不管我们如何看待,CPU在执行add等指令的时候,就已经包含了两种含义,也将得到用同一种信息来记录的两种结果。关键在于我们在程序需要哪一种结果。
SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。
在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。
如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。
这也就是说,CPU在执行add等指令时,是必须要影响到SF标志位的值的。至于我们需不需要这种影响,那就看我们如何看待指令所进行的运算了。
某些指令将影响标志寄存器中的多个标志位,这些被影响的标记位比较全面地记录了指令的执行结果,为相关的处理提供了所需的依据。
比如指令sub al,al执行后,ZF、PF、SF等标志位都要受到影响,它们分别为:1、1、0,分别表示结果为零、结果二进制数1的个数为偶数、结果不为负数。
CF标志 [C,Carry进位、F,Flag标志]
flag的第0位是CF,进位标志位。一般情况下,在进行了无符号运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
对于倍数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。
当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。
由于这个进位值有可能无法保存,我们在前面的课程中,就只是简单地说这个进位值丢失了,其实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记录了向更高位的借位值
OF标志 [O,Overflow溢出,F,Flag标志]
溢出:在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。
那么,什么是机器所能表示的范围呢?
比如说,指令运算的结果用8位寄存器或内存单元来存放,比如:add al,3,那么对于8位的有符号数据,机器所能表示的范围就是-128~127。同理,对于16位有符号数,机器所能表示的范围是-32768~32767。
注意,这里所讲的溢出,只是对有符号数运算而言。
由于在进行有符号数运算时,可能发生溢出而造成结果的错误,则CPU需要对指令执行后是否产生溢出进行记录。
flag的第11位是OF,溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。如果发生溢出,OF=1,如果没有,OF=0。
一定要注意CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位。
比如:
mov al,98d
add al,99d
add指令执行后:CF=0,OF=1。
CPU在执行add等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。
对于无符号数运算,CPU用CF位来记录是否产生了进位;
对于有符号数运算,CPU用OF位来记录是否产生了溢出,
当然,还要用SF位来记录结果的符号。
对于无符号数运算,98+99没有进位,CF=0;
对于有符号数运算,98+99发生溢出,OF=1。
CF和OF所表示的进位和溢出,是分别对无符号数和有符号数运算而言的,它们之间没有任何关系。
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。
adc指令比add指令多加了一个CF位的值。
为什么要加上CF的值呢?
CPU为什么要提供这样一条指令呢?
CF的值的含义,在执行adc指令的时候加上的CF的值的含义,由adc指令前面的指令决定的,也就是说,关键在于所加上的CF的值是被什么指令设置的。显然,如果CF的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。
加法可以分两步来进行:(1)低位相加;(2)高位相加再加上低位相加产生的进位值。
下面的指令和add 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 ;高16位
mov bx,0f000H ;低16位
add bx,1000H ;低16位相加
adc ax,0020H ;高16位相加,并加上CF进位值。
adc指令执行后,也可能产生进位值,所以也会对CF位进行设置。
由于有这样的功能,我们就可以对任意大的数据进行加法运算。
inc和loop指令不影响CF位。
sbb指令
sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1-操作对象2-CF
比如指令 sbb ax,bx 实现的功能是:(ax) = (ax) – (bx) – CF。
sbb指令执行后,将对CF进行设置。
利用sbb指令我们可以对任意大的数据进行减法运算。
比如,计算003E1000H – 00202000H,结果放在ax,bx中,程序如下:
mov bx,1000H
mov ax,003EH
sub bx,2000H ;(bx) = (bx)-2000H,CF设置借位数 1
sbb ax,0020H ;(ax) = (bx) – 0020H – CF
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
cmp ax,bx
执行后:(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) 将产生借位,所以: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。
现在我们可以看出比较指令的设计思路,即:通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较的结果。反过来看上面的例子:
指令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来进行有符号数比较时,CPU用哪些标志位对比较结果进行记录。
cmp ah,bh
如果(ah)=(bh) 则(ah)-(bh)=0,所以ZF=1;
如果(ah)≠(bh) 则(ah)-(bh)≠0,所以ZF=0。
对于有符号数运算,在(ah)<(bh)情况下,(ah)-(bh)显然可能引起SF=1,即结果为负。
比如:
(ah)=1,(bh)=2;则(ah) – (bh)=0FFH,0FFH为-1的补码,因为结果为负,所以SF=1。
(ah)=0FEH,(bh)=0FFH;则(ah) – (bh)= – 2 – (–1)=0FFH,因为结果为负,所以SF=1。
(ah)=22H,(bh)=0A0H;则(ah) – (bh)=34 – (-96)=82H,82H是-126的补码,所以SF=1。
两个有符号娄A和B相减,得到的是负数,那么可以肯定A<B,这个思路没有错误,关键在于我们根据什么来断定得到的是一个负数。CPU将cmp指令得到的结果记录在flag的相关标志位中。我们可以根据指令执行后,相关标志位的值来判断比较的结果。单纯地考察SF的值不可能知道结果的正负。因为SF记录的只是可以在计算机中存放的相应位数的结果的正负,比如add ah,al执行后,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不能说明在逻辑上运算所得的正确结果的正负。
但是逻辑上的结果的正负,才是cmp指令所求的真正结果,因为我们就是要靠它得到两个操作对象的比较信息。所以cmp指令所作的比较结果,不是仅仅靠SF就能记录的,因为它只能记录实际结果的正负。
两种结果之间的关系,实际结果的正负,和逻辑上真正结果的正负,它们之间有多大的距离呢?
实际结果的正负,之所以不能说明逻辑上真正结果的正负,关键的原因在于发生了溢出。如果没有溢出发生的话,那么,实际结果的正负和逻辑上真正结果的正负就一致了。
所以,我们应该在考察SF(得知实际结果的正负)的同时考察OF(得到有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。
总结,cmp ah,bh
1) 如果SF=1,而OF=0
OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;
因SF=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。
2) 如果SF=1,而OF=1
OF=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
因SF=1,实际结果为负。
实际结果为负,而又有溢出,这说明是由于溢出导致了实际结果为负,简单分析一下,就可以看出,如果因溢出导致了实际结果为负,那么逻辑上真正的结果必须为正。
这样,SF=1,OF=1,说明了(ah)>(bh)。
3) 如果SF=0,而OF=1
OF=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
因SF=0,实际结果非负,而OF=1说明有溢出,则结果非0,所以实际结果为正。
实际结果为正,而又有溢出,这说明是由于溢出导致了实际结果非负,简单分析一下,就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必须为负。
这样,SF=0,OF=1,说明了(ah)<(bh)。
4) 如果SF=0,而OF=0
OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;
因SF=0,实际结果非负,所以逻辑上真正的结果非负,所以(ah)≥(bh)。
8086CPU这种工作机制的设计思想,实际上,对于各种处理机来说是普遍的。
检测比较结果的条件转移指令
“转移”指的是它能够修改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 不大于
编程实现如下功能:
如果(ah)=(bh)则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)。
cmp ah,bh
je s
add ah,bh
jmp short ok
s:add ah,ah
ok:…
上面的程序执行时,如果(ah)=(bh),则cmp ah,bh 使ZF=1,而je检测ZF是否为1,如果为1,将转移到标号s处执行指令 add ah,ah。这也可以说,cmp比较ah、bh后所得到的相等的结果使得je指令进行转移。从而很好地体现了je指令的逻辑含义,相等则转移。
虽然je的逻辑含义是“相等则转移”,但它进行的操作是,ZF=1时则转移。
“相等则转移”这种逻辑含义,是通过和cmp指令配合使用来体现的,因为cmp指令为“ZF=1”赋予了“两数相等”的含义。
至于究竟在je之前使不使用cmp指令,在于我们在安排je检测的是ZF位置,不管je前面是什么指令,只要CPU执行je指令时,ZF=1,那么就会发生转移。
如何配合使用这些指令,完全取决于程序作者。
虽然我们分别讨论了cmp指令和与其比较结果相关的有条件转移指令,但是它们经常在一起配合使用。所以我们在联合应用它们的时候,不必再考虑cmp指令对相关标志位的影响和je等指令对相关标志位的检测。因为相关的标志位,只是为cmp和je等指令传递比较结果。我们可以直接考虑cmp和je等指令配合使用时,表现出来的逻辑含义。它们在联合使用的时候表现出来的功能有些像高级语言中的IF语句。
上面讲解了根据无符号数的比较结果进行转移的条件转移指令。根据有符号数的比较结果进行转移的条件转移指令的工作原理和无符号的相同,只是检测了不同的标志位。
cmp、标志寄存的相关位、条件转移指令三者配合应用,这个原理具有普遍性。
DF标志和串传送指令
flag的第10位是DF,Direction Flag,方向标志位。
在串处理指令中,控制每操作后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] ;8086CPU并不支持这样的指令,这里只是个描述。
如果DF=0:
inc si
inc di
如果DF=1:
dec si
dec di
movsb指令的功能是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器DF位的值,将si和di递增或递减。
也可以传送一个字,指令如下:
格式:movsw
movsw的功能是将ds:si指向的内存单元中word送入es:di中,然后根据标志寄存器DF位的值,将si和di递增2或递减2。
用汇编语法描述movsw的功能如下:
mov es:[di], word ptr ds:[si] ;8086CPU并不支持这样的指令,这里只是个描述。
如果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的值,重复执行后面的串传送指令。
由于每执行一行movsb指令,si和di都会递增或递减指向后一个单元或前一个单元,则rep movsb就可以循环实现(cx)个字符的传送。
同理,movsw也有类似功能。
由于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
mov ax,data
mov ds,ax
mov si,0 ;ds:si指向data:0
mov es,ax
mov di,16 ;es:di指向data:16
mov cx,16 ;(cx)=16, rep循环16次
cld ;设置DF=0,正向传送
rep movsb
pushf和popf
pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。
pushf和popf,为直接访问标志寄存器提供了一种方法。
标志寄存器在Debug中的表示
在Debug中,标志寄存器是按照有意义的各个标志位单独表示的。