7. 标志寄存器

158 阅读10分钟

标志寄存器

CPU内部的寄存器中,有一种特殊的寄存器具有以下3种作用。

  1. 用来存储相关指令的某些执行结果;
  2. 用来为CPU执行相关指令提供行为依据;
  3. 用来控制CPU的相关工作方式。

这种特殊的寄存器被称为标志寄存器。8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)

flag和其他寄存器不一样,其它寄存器都用来存放数据,都是整个寄存器具有一个含义。而flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息

8086CPU的flag寄存器的结构如下所示:

1514131211109876543210
OFDFIFTFSFZFAFPFCF

没有标出来的位再8086CPU中没有使用,不具有任何含义。

1. ZF标志

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等,它们大都是传送指令。

2. PF标志

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

比如,指令:

 mov al, 1
 add al, 10

执行后,结果为00001011B,其中有3个1,则pf = 0。

3. SF标志

flag的第七位是SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,sf=1;如果非负,sf=0。在计算机中,无符号数的运算与有符号数的补码运算是一致的,关键在于我们如何去解读这个数据。SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知数据的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。

比如:

 mov al, 10000001B
 add al, 1

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

4. CF标志

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

对于位数为N的无符号数来说,它的最高有效位就是第N-1位。

我们知道,当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如,两个8位数据:98H+98H,将产生进位。由于这个进位值在8位寄存器中无法保存,就用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, CH = 1, CH记录了向更高位的借位值
 sub al, al    ;执行后:(al) = 0, CF = 0, CF记录了向更高位的借位值

可以发现,把CF位的值和运算结果拼起来,就是正确完整的计算结果

5. 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 = 0;对于有符号数运算,98+99发生溢出, OF = 1。

 mov al, 0F0H
 add al, 88H

add指令被执行后:CF = 1, OF = 1。对于无符号数运算,0F0H + 88H有进位,CF = 1;对于有符号数运算, 0F0H + 88H发生溢出,OF = 1。

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

6. adc指令

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

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

功能:操作对象1 = 操作对象1 + 操作对象2 + CF

因为加法可以分两步来进行:1. 低位相加;2. 高位相加再加上低位相加产生的进位值。所以利用adc指令和add指令相配合就可以对更大的数据进行加法运算。

下面编写一个子程序,对两个128位数据相加。

;名称: add128
;功能:两个128位数据相加
;参数:ds:si指向存储第一个数的内存空间,因数据为128位,所以需要8个字单元,
;由低地址单元到高地址单元依次存放128位数据由低到高的各个字。
;运算结果存储在第一个数的存储空间中

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

注意:以上代码中不能把"inc si"替换成"add si, 2",因为这样有可能改变CF的值;而inc和loop指令是不影响CF的值的。

7. sbb指令

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

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

功能:操作对象1 = 操作对象1 - 操作对象2 - CF

8. cmp指令

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

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

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

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

所有条件转移的指令的转移位移都是[-128,127]。

下面是常用的根据无符号数的比较结果进行转移的条件转移指令

指令含义检测的相关标志位
je等于则转移zf = 1
jne不等于则转移zf = 0
jb小于则转移cf = 1
jnb不小于则转移cf = 0
ja大于则转移cf = 0且 zf = 0
jna不大于则转移cf = 1 或 zf = 1

10. DF标志和串传送指令

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

df = 0 每次操作后si、di递增

df = 1 每次操作后si、di递减

我们接着看串传送指令。

格式: movsb

功能:执行movsb相当于进行下面几步操作:

  1. ((es)x16+(di)) = ((ds)x16+(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 和以上指令类似,区别是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)个字符的传送。

8086CPU提供下面两条指令对df位进行设置。

cld指令:将标志寄存器的df位置0

std指令:将标志寄存器的df位置1

下面提供一个范例程序。

编程,用串传送指令,将data段中的第一个字符复制到它后面的空间中。

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

我们分析一下:使用串传送指令进行数据的传送,需要给它提供一些必要的信息,它们是

  • 传送的原始位置:ds:si
  • 传送的目标位置:es:di
  • 传送的长度:cx
  • 传送的方向:df
assume cs:code

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

code segment
  start: mov ax, data
         mov ds, ax
         mov si, 0
         mov es, ax
         mov di, 10H
         mov cx, 16
         cld               ;设置df = 0,正向传送
         rep movsb
         
         mov ax, 4c00H
         int 21
code ends

end start

11. pushf和popf

pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。

pushf和popf为直接访问标志寄存器提供了一种方法。

12. 标志寄存器在Debug中的表示

在Debug中,标志寄存器是按照有意义的各个标志位单独表示的。

标志值为1的标记值为0的标记
ofOVNV
sfNGPL
zfZRNZ
pfPEPO
cfCYNC
dfDNUP

13. 实验11 编写子程序

编写一个子程序,将包含任意字符,以0结尾的字符串中的小写字母转变为大写字母,描述如下:

名称:letterc

功能:将以0结尾的字符串中的小写字母转变为大写字母

参数:ds:si指向字符串首地址

assume cs:code

data segment
  db "Beginner's All-purpose Symbolic Instruction Code.",0
data ends

code segment
  start: mov ax, data
         mov ds, ax
         mov si, 0
         call letterc
         mov ax, 4c00H
         int 21H
         
letterc: cmp byte ptr [si], 0
         je over                   ;等于0则结束
         cmp byte ptr [si], 61H
         jb next                   ;ASCII码小于61H则跳过
         cmp byte ptr [si], 7AH
         ja next                   ;ASCII码大于7AH则跳过
         add byte ptr [si], -20H
   next: inc si
         jmp short letterc
   over: ret
code ends

end start