汇编语言学习笔记(十一):标志寄存器

学习参考:汇编语言(第2版)王爽

  • ZF 标志
  • pf 标志
  • SF 标志
  • CF 标志
  • OF 标志
  • adc 指令
  • sbb 指令
  • cmp 指令
  • 检测比较结果的条件转移指令
  • DF 标志和串传送指令
  • pushf 和 popf

CPU 内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下 3 种作用

  • 用来存储相关指令的某些执行结果
  • 用来为 CPU 执行相关指令提供行为依据
  • 用来控制 CPU 的相关工作方式

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

标志寄存器(以下简称 flag)和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而 flag 寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。8086CPU 的 flag 寄存器的结构如下图

flag 的 1、3、5、12、13、14、15 位在 8086CPU 中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11 位都具有特殊的含义

注意 在8086CPU 的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and 等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对寄存器没有影响,比如,mov,push、pop等,它们大都是传送指令。

在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的哪些标志位造成影响

ZF 标志

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

1
2
3
4
5
6
7
8
mov ax,1
sub ax,1
; 结果为 0 ,zf=1

mov ax,2
sub ax,1
; 结果不为 0,zf=0

pf 标志

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

1
2
3
4
5
6
7
8
9
10
mov al,1
add al,10
; 结果为 00001011B,3 个 1,pf=0

mov al,1
or al,2
; 结果为 00000011B,2 个 1,pf=1

sub al,al
; 结果为 00000000B,0 个 1,pf=1

SF 标志

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

1
2
3
4
5
6
7
8
mov al,10000001B
add al,1
; 结果 10000010B,sf=1,表示:如果指令进行的是有符号数运算,那么结果为负


mov al,10000001B
add al,01111111B
; 结果 0,sf=0,表示指令进行的是有符号数运算,那么结果为非负

CF 标志

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

当两数相加时,有可能产生进位,CPU 在运算的时候,将这个进位值记录在一个特殊的寄存器的某一位上。8086CPU 就用 flag 的 CF 位来记录这个进位值。当两数相减时,有可能向更高位借位,而 flag 的 CF 位也可以用来记录这个借位值。

1
2
3
4
5
6
7
8
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记录了从最高有效位向更高位的进位值

OF 标志

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

对 8 位有符号数据,机器所能表示的范围就是 -128127。同理,对于 16 位有符号数据,机器所能表示的范围是 -3276832767

如果超出了及其所能表达的范围,将产生溢出

flag 的第 11 位是 OF(Overflow Flag)。一般情况下,OF 记录了有符号数运算的结果是否发生了溢出。如果发生了溢出,OF=1;如果没有,OF=0

注意 注意 CF 和 OF 的区别:

  • CF 是对无符号数运算有意义的标志位
  • OF 是对有符号数运算有意义的标志位
1
2
3
4
mov al,98
add al,99

; add指令执行后:CF=0,OF=1

CPU 在执行 add 等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。对于无符号数运算,CPU 用 CF 位来记录是否产生了进位;对于有符号位运算,CPU 用 OF 位来记录是否产生了溢出,当然,还要用 SF 位来记录结果的符号。对于无符号数运算,98+99 没有进位,CF=0;对于有符号数运算,则发生了溢出,OF=1。

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

adc 指令

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

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

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,2
mov bx,1
sub bx,ax
adc ax,1

; 执行后,(ax)=4。adc 执行时,相当于计算:(ax)+1+CF=2+1+1=4


mov ax,1
add ax,ax
adc ax,3
; 执行后,(ax)=5。adc 执行时,相当于计算:(ax)+3+CF=2+3+0=5

Q:那么为什么 CPU 要提供这样一条指令?
A:先来看一下CF的值的含义。在执行adc指令的时候加上的 CF 的值的含义,是由adc 指令前面的指令决定的,也就是说,关键在于所加上的 CF 值是被什么指令设置的。显然,如果 CF 的值是被 sub 指令设置的,那么它的含义就是借位值; 如果是被 add 指令设置的,那么它的含义就是进位值。两个十六进制数相加时,可以分为两步来进行:1.低位相加;2.高位相加再加上低位相加产生的进位值。adc指令的目的,就是来进行加法的第二步运算的。adc 指令和 add 指令相配合就可以对更大的数据进行加法运算。

sbb 指令

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

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

sbb 指令执行后,将对 CF 进行设置。利用 sbb 指令可以对任意大的数据进行减法运
算。比如,计算 003E1000-00202000H,结果放在 ax,bx 中,程序如下:

1
2
3
4
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

下面的指令:

1
2
3
4
mov ax,8
mov bx,3
cmp ax,bx
;执行后:(ax)=8, zf=0, pf=1, sf=0, cf=0, of=0

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

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

因为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

通过结合 cmp 指令和条件转移指令就可以实现类似高级语言中 IF 的效果

编程 如果 (ah)=(bh) 则 (ah)=(ah)+(ah),否则 (ah)=(ah)+(bh)

1
2
3
4
5
6
    cmp ah,bh
je s
add ah,bh
jmp short ok
s: add ah,ah
ok:

DF 标志和串传送指令

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

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

介绍一个串传送指令

格式:movsb (MOV String Byte)
功能:执行 movsb 指令相当于进行下面几步操作

  • ((es)*16+(di))=((di)*16+(si)) 把 ds:di 指向的内存单元中的字节送入 es:di 中
  • 如果 df=0,则 (si)=(si)+1 ; (di)=(di)+1
  • 如果 df=1,则 (si)=(si)-1 ; (di)=(di)-1

当然也可以传送一个字,使用指令 movsw (MOV String Word),不过这里 di 和 si 的递增或递减的数量是 2

movsb 和 movsw 都和 rep 配合使用,格式如下:

1
rep movsb

用汇编语法来描述该功能就是:

1
2
s:  movsb
loop s

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

movsw 同理

下面两条指令对 df 位进行色设置

cld 指令 (CLear Direction flag):将标志位寄存器的 df 位置 0
std 指令 (SeT Direction flag):将标志位寄存器的 df 位置 1

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

1
2
3
4
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. 传送的方向:df=0

核心代码如下

1
2
3
4
5
6
7
8
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 ; 设置 df=0,rep 循环 16 次
cld ; 设置 df=0,正向传递
rep movsb

pushf 和 popf

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

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

Author: Inno Fang
Link: http://innofang.github.io/2017/11/28/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%8D%81%E4%B8%80%EF%BC%89%EF%BC%9A%E6%A0%87%E5%BF%97%E5%AF%84%E5%AD%98%E5%99%A8/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-ND 4.0 unless stating additionally.