标志寄存器
CPU内部的寄存器中,有一种特殊的寄存器具有以下3种作用。
- 用来存储相关指令的某些执行结果;
- 用来为CPU执行相关指令提供行为依据;
- 用来控制CPU的相关工作方式。
这种特殊的寄存器被称为标志寄存器。8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW) 。
flag和其他寄存器不一样,其它寄存器都用来存放数据,都是整个寄存器具有一个含义。而flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
8086CPU的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 |
没有标出来的位再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相当于进行下面几步操作:
-
((es)x16+(di)) = ((ds)x16+(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递增或递减。
当然也可以传送一个字,指令如下:
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的标记 |
|---|---|---|
| of | OV | NV |
| sf | NG | PL |
| zf | ZR | NZ |
| pf | PE | PO |
| cf | CY | NC |
| df | DN | UP |
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