Flag寄存器

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寄存器各位示意图

汇编语言-11标志寄存器_汇编技术

flag的1、3、5、12、13、14、15位在8086CPU中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11位都具有特殊的含义,分别代表CF、PF、ZF、SF、OF、DF标志位。

汇编语言-11标志寄存器_汇编技术_02

ZF标志

flag的第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0。如果结果为0,那么zf=1;如果结果不为0,那么zf=0。

mov ax,l
sub ax,l

执行后,结果为0, 则zf=1。

mov ax,2
sub ax,1

执行后,结果不为0, 则zf=0 。

在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令。在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的哪些标志位造成影响。

PF标志

flag的第2位是PF,奇偶标志位。它记录相关指令执行后,其结果的所有bit位中l的个数是否为偶数。如果l的个数为偶数,pf=1,如果为奇数,那么pf=0

汇编语言-11标志寄存器_汇编技术_03

SF标志

flag的第7位是SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,sf=1;如果非负,sf=0。

计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。

汇编语言-11标志寄存器_汇编技术_04

 

这也就是说,对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。

mov al ,10000001B
add al ,1
结果:(al)=10000010B 

汇编语言-11标志寄存器_汇编语言_05

 

mov al,10000001B
add al,1

执行后,结果为10000010B,sf=1,表示:指令进行的是有符号数运算,结果为负;

mov al,10000001B
add al,01111111B

执行后,结果为0,sf=0,表示:指令进行的是有符号数运算,结果为非负。

某些指令将影响标志寄存器中的多个标记位,这些被影响的标记位比较全面地记录了指令的执行结果,为相关的处理提供了所需的依据。比如指令sub al,al 执行后,ZF、PF、SF等标志位都要受到影响,它们分别为:1、1、0。

CF标志

flag的第0位是CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。

对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。

汇编语言-11标志寄存器_汇编技术_06

 

当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如,两个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 记录了向更高位的借位值

OF标志

在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。

对于8位的有符号数据,机器所能表示的范围就是-128~127。同理,对于16位有符号数据,机器所能表示的范围是-32768~32767。

如果运算结果超出了机器所能表达的范围,将产生溢出。注意,这里的溢出,只是对有符号数运算而言。

mov al,98
add al,99

结果197超出了机器所能表示的8位有符号数的范围:-128 ~ 127

mov al,0F0H  ; F0H, 为有符号数-16 的补码
add al,088H  ; 88H, 为有符号数-120 的补码

+负数可以转换为加上这个负数的补码.

结果-136超出了机器所能表示的8位有符号数的范围:-128~127

由于在进行有符号数运算时,可能发生溢出而造成结果的错误。则CPU需要对指令执行后是否产生溢出进行记录。

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 位来记录是否产生了溢出,当然,还
要用SF位来记录结果的符号。对于无符号数运算,98+99没有进位,CF=O;对于有符号数运算,98+99发生溢出,OF=1。

汇编语言-11标志寄存器_汇编语言_07

CF和OF所表示的进位和溢出,是分别对无符号数和有符号数运算而言的,它们之间没有任何关系。

adc指令

adc是带进位加法指令,它利用了CF位上记录的进位值。

指令格式:adc 操作对象1,操作对象2

功能:操作对象l=操作对象l+操作对象2+CF
比如指令adc ax,bx实现的功能是:(ax)=(ax)+(bx)+CF

汇编语言-11标志寄存器_汇编技术_08

CF的值的含义。在执行adc指令的时候加上的CF的值的含义,是由adc指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。显然,如果CF的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。

0198H和0183H如何相加:

汇编语言-11标志寄存器_汇编语言_09

add ax, bx等同于:

add al,bl    #al=al+bl,如果有进位则CF=1
adc ah,bh    #ah=ah+bh+CF

CPU提供adc指令的目的,就是来进行加法的第二步运算的。adc指令和add指令相配合就可以对更大的数据进行加法运算。

计算1EF000H + 201000H

计算1EF000H + 201000H,结果放在ax(高16位)和bx(低16位)中。

mov ax,001EH   #ax放第一个数的高位部分
mov bx,0F000H   #bx放第一个数的低位部分,十六进制第一位不能是字母所以补0
add bx,1000H    #bx加上第二个数的低位部分,进位CF=1
adc ax,0020H    #ax加上第二个数的高位部分+CF

adc指令执行后,也可能产生进位值,所以也会对CF位进行设置。有这样的功能,我们就可以对任意大的数据进行加法运算。

计算1EF0001000H + 2010001EF0H

计算1EF0001000H + 2010001EF0H,结果放在ax(最高16位),bx(次高16位),cx(低16位)中。

汇编语言-11标志寄存器_汇编语言_10

mov ax,001EH    #第一个数的的最高位16位
mov bx,0F000H    #第一个数的次高16位
mov cx,1000H    #第一个数的低16位
add cx,1EF0H  #cx加上第二个数的低16位,CF记录是否进位
adc bx,1000H  #bx加上第二个数的次高16位,CF记录是否进位
adc ax,0020H  #ax加上第二个数的最高16位,CF记录是否进位

两个128 位数据相加

汇编语言-11标志寄存器_汇编技术_11

 

 实现

汇编语言-11标志寄存器_汇编技术_12

sbb指令

sbb是带借位减法指令,它利用了CF位上记录的借位值。

指令格式:sbb 操作对象1,操作对象2

功能:操作对象1 = 操作对象1 - 操作对象2 - CF
比如指令sbb ax,bx实现的功能是:(ax) = (ax) - (bx) - CF

汇编语言-11标志寄存器_汇编语言_13

cmp指令

cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

cmp指令格式:cmp 操作对象1,操作对象2
功能:计算操作对象1-操作对象2但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。

cmp无符号数

指令cmp ax,ax,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。指令执行后:zf=1,pf=1,sf=0,cf=0,of=0。

汇编语言-11标志寄存器_汇编语言_14

cmp ax,bx(无符号数)

汇编语言-11标志寄存器_汇编技术_15

指令cmp ax,bx(无符号数)的逻辑含义是比较ax和bx中的值,如果执行后:

汇编语言-11标志寄存器_汇编语言_16

同add、sub指令一样,CPU在执行cmp指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算。所以利用cmp指令可以对无符号数进行比较,也可以对有符号数进行比较。上面的是用cmp进行无符号数比较时,相关标志位对比较结果的记录。

cmp有符号数

cmp ah,bh

汇编语言-11标志寄存器_汇编语言_17

对于有符号数运算,在(ah)<(bh)情况下,(ah) - (bh)显然可能引起sf=1, 即结果为负。例如:

汇编语言-11标志寄存器_汇编技术_18

cmp 操作对象1,操作对象2,指令执行后,如果sf=1,并不能说明操作对象1 < 操作对象2。

汇编语言-11标志寄存器_汇编技术_19

sf只记录结果的正负,当有符号运算的计算结果越界后符号位就不准确了,而sf依然只看符号位去记录值。

汇编语言-11标志寄存器_汇编语言_20

08AH-070H = -118-112 = -230 = FF1A = 1111 1111 0001 1010,符号位为0,所以sf=0;

汇编语言-11标志寄存器_汇编技术_21

总结:

汇编语言-11标志寄存器_汇编技术_22

检测比较结果的条件转移指令

“转移”指的是它能够修改IP,而“条件”指的是它可以根据某种条件,决定是否修改IP。

jcxz就是一个条件转移指令,它可以检测cx中的数值,如果(cx)=O,就修改IP,否则什么也不做。所有条件转移指令的转移位移都是[-128,127]。

除了jcxz之外,大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改IP。

条件转移指令通常都和cmp相配合使用,就好像call和ret指令通常相配合使用一样。

根据无符号数的比较结果进行转移的条件转移指令

汇编语言-11标志寄存器_汇编技术_23

第一个字母都是j,表示jump;后面的字母表示意义如下。

汇编语言-11标志寄存器_汇编语言_24

je检测的是zf位置,不管je前面是什么指令,只要CPU 执行je指令时,zf=1, 那么就会发生转移:

汇编语言-11标志寄存器_汇编技术_25

统计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    #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

DF标志和串传送指令

DF标志(Direction Flag)

flag的第10 位是DF, 方向标志位。在串处理指令中,控制每次操作后si 、di 的增减。

df=0 每次操作后si、di 递增;
df=1 每次操作后si、di 递减。

串传送指令

movsb(move string byte)

格式: 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:
dee si
dee di

movsw(move string word)

格式: movsw

movsw 的功能是将ds:si指向的内存字单元中的字送入es: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
loops

rep的作用是根据cx的值,重复执行后面的串传送指令。由于每执行一次movsb指令si和di会递增或递减指向后一个单元或前一个单元,则rep movsb就可以循环实现(cx)个字符的传送。

同理,也可以使用这样的指令: rep movsw。相当于:s :movsw    loop s

DF位操作指令

由于flag的df位决定着串传送指令执行后,si和di改变的方向,所以CPU应该提供相应的指令来对df位进行设置,从而使程序员能够决定传送的方向。

8086CPU提供下面两条指令对df 位进行设置:
cld指令(clear direction): 将标志寄存器的df位置0
std指令(set direction):将标志寄存器的df位置1

将data段中的第一个字符串复制到它后面的空间

data segment
  db 'Welcome to masm!'
  db 16 dup (0)
data ends

分析

汇编语言-11标志寄存器_汇编技术_26

 代码

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    #(ex)=16,rep循环16次
cld    #设置df=0,正向传送
rep movsb

pushf和popf

pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。
pushf和popf,为直接访问标志寄存器提供了一种方法。

标志寄存器在Debug中的表示

汇编语言-11标志寄存器_汇编技术_27

 

各个标记的解释

汇编语言-11标志寄存器_汇编技术_28