前言
点赞再看,养成习惯!
今天我们来讲一下常见CPU组成中的最后一个寄存器:标志寄存器.
一、标志寄存器的简介
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的CPU可能其构造和数量均不同),其主要有三中作用:
- 用来存储相关指令的某些执行结果
- 用来为CPU执行相关指令提供行为依据
- 用来控制CPU的相关工作方式
这种寄存器在我们8086CPU 中被称为标志寄存器。8086CPU 的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW) ,与其他寄存器不同的是,其他寄存器都是整个寄存器用来存放特定具数据,而标志寄存器则是按照每一位来记录特定信息。比如8086CPU 的结构图如下:
flag寄存器中的1,3,5,12,13,14,15位在8086CPU 中并没有被应用,不具备任何含义。而剩余各位则都具有其特殊含义,接下来我们就要详细的讲一下剩下的其他各位。
二、标志位详解
2.1 ZF标志
ZF标志位 是flag寄存器的第六位,我们可以将其理解为零标志位。它主要是用来记录相关指令执行后,其结果是否为0,如果结果为0则 zf = 1 否则为0。比如我们可以执行如下程序:
assume cs:code
code segment
main:
mov ax,1
sub ax,1
code ends
end main
运行结果: 运行结果中的nz 代表当前结果值非0,zr 代表当前结果值为0。
2.2 PF标志
奇偶标志位,它负责记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数,如果1为偶数则pf=1 反之为0。
assume cs:code
code segment
main : mov al,2
add al,10
code ends
end main
运行结果: 我们通过运行结果对比可以知晓:PO 代表基数,PE 代表偶数
2.3 SF标志
flag寄存器的第七位是符号标志位,它用来标志程序的运行结果是否为负,若为负则sf=1,反之为0。
assume cs:code
code segment
main : mov al,1
sub al,2
code ends
end main
运行结果: 我们通过运行结果对比可以知晓:NC 代表整数,CY 代表负数
2.4 CF标志
在学习CF标志之前,我们要先搞清楚一个基础概念:有符号运算及无符号运算
2.4.1 无符号运算
什么是无符号运算?无符号运算更像是数的绝对值,比如我们的二进制串1111 1111 在无符号运算的时候其表达的含义就是十进制的255 ,此时用来参与运算的1111 1111 是不会考虑正负的。
2.4.2 有符号运算
但是同样是1111 1111 如果我们想要其表达出正负,按照约定我们就需要通过二进制串的第一位来表示数的正负,正数为0,负数为1。同时为了通过加法代替减法(比如 1 + (-1) 等价于1 - 1 ),负数在表达时会采用其真码 的补码 来进行表示。比如我们说数字-1 ,其真码 就是1000 0001 ,其反码 就是1111 1110 ,进一步我们能推出其补码 就是1111 1111 。是不是很熟悉,没错这就是我们无符号运算中的255 。
我们再回到CF标志位,CF标志位处于flag寄存器的第0位,其主要是用来标识无符号计算时运算结果向更高位进位(或借位)的状态。比如8位寄存器 中我们进行一个简单的加法:十六进制的 80 + 7F ,对应的程序源码就是:
assume cs:code
code segment
main : mov al,80H
mov bl,7FH
add al,bl
code ends
end main
运行结果:
那么如果是无符号运算的80+80 呢?我们再来试一下:
assume cs:code
code segment
main : mov al,80H
mov bl,80H
add al,bl
code ends
end main
运行结果: 我们预期的结果应该是十六进制的100 ,但是此时由于AL 寄存器只能显示八位数字,因此只能显示出低位的00 ,而消失不见的1 此时其实就由CF标志位进行标注。
2.5 OF标志
OF标志位处于flag寄存器的第11位,它的作用是记录有符号运算时的溢出情况,比如我们一个8位寄存器 可以表达十进制有效范围为:+127 到 -128 ,那如果我们进行的运算超过了这个范围怎么办?这种情况我们就称之为发生了模溢出 ,此时我们就需要使用OF寄存器进行记录。 我们简单的通过计算十进制的127+1(即十六进制的7F+1)来模拟一下溢出场景。
assume cs:code
code segment
main : mov al,7fH
mov bl,1H
add al,bl
code ends
end main
运行结果: 此时运算结果为二进制的1000 0000 即十进制的-128 。很明显不符合我们的预期,这就是由于值溢出导致的。
2.5.1 CF标志及OF标志的区别
很多小伙伴 其实并不能很好的区分CF标志位的进位和OF标志位的溢出的概念。首先如果我们使用的是8位寄存器,那么其结构应该如下图所示: 我们以数据1111 1111 为例,如果是无符号运算,此时数据结构如下: 此时八位数据全部参与运算,其10进制值为255 ,如果此时我们将该数据+1 ,则结果应是: 此时结果值为十进制的256 ,而由于索引为8的内存单元已经超过了寄存器的长度限制,因此需要借位,因此CF标志需要更改为1,表示发生了借位。
我们再来看下如果此时进行的是符号运算,则同样是元素1111 1111 ,由于其首位为符号位1 表示负数,因此其表现形式为补码,其真值则为-1 。
如果此时我们将值+1 则其结果为: 此时由于第七位就是我们的符号位,因此溢出的第八位我们无需关注,因此此时数据0000 0000 表示的值便是十进制的0 ,此时还并没有发生值的溢出。如果我们此时将数据更改为0111 1111 ,其符号位为正数,则其真值为十进制的+127 。
此时值+1则为1000 0000 ,符号位发生了改变,其真值为十进制的- 128 ,明显不符合我们127 + 1 = 128 ,发生了值溢出。
此时按照规定,OF符号位的值应为1 。
2.6 DF标志
DF标志是Flag寄存器的第十位,被称为方向标志位,其作用就是我们在进行串操作的时候进行SI ,DI 的递减,如果df=0 则每次操作后si,递增;如果df=1 ,每次操作后si,di递减。其具体应用我们会在稍后通过新的指令进行讲解。
2.7 DOSBOX中的flag标志位表示
三、Flag标志位的衍生指令
3.1 adc指令
adc 指令是带进位加法指令,它就是利用了CF标志位 的进位功能。
指令格式: adc 对象1,对象2 如: adc ax,bx 作用: 对象1 = 对象1+对象2 +cf 如:adc ax,bx -> ax = ax + bx +cf 我们来简单的写个程序:
assume cs:code
code segment
main : mov ax,1
mov bx,2
sub ax,bx ; 此时ax = ffff 发生了借位,cf = 1
adc ax,bx ; ax = ffff + 2 + 1 = 2 此时发生进位,cf=1
code ends
end main
3.2 sbb指令
sbb 指令是带进位(借位)信息的减法指令,它与adc 指令类似,也是利用CF的值进行计算。
指令格式: sbb 对象1,对象2 如: sbb ax,bx 作用: 对象1 = 对象1 - 对象2 - cf 如:sbb ax,bx -> ax = ax - bx - cf 我们来简单的写个程序:
assume cs:code
code segment
main : mov ax ,2
mov bx ,1
sbb bx,ax ; bx = ffff 此时发生借位 cf = 1
sbb ax,ax ; ax = ax - ax - 1 = ffff
code ends
end main
3.3 cmp指令
cmp 是比较指令,其功能相当于减法指令,只不过减法的值并不会被保存到寄存器中。当cmp 指令执行后,其比较结果会影响flag寄存器 ,这样我们就可以通过flag寄存器获知比较的结果(主要影响CF,ZF,OF,AF,PF)。那我们该如何判断比较大小的结果呢? 比如:
mov ax,1
mov bx,1
cmp ax,bx
3.3.1 相等
由于cmp ax,bx = ax - bx = 0 因此 zf = 1 说明 ax 等于bx
3.3.2 无符号比较
比如 cmp ax,bx 若CF = 1 由于cmp 进行的是减法运算,因此表示此时运算发生了借位,因此ax 小于 bx 反之若CF= 0 则ax >= bx(需要判断zf的值来判断是否相等)
3.3.3 有符号比较
如 cmp ax,bx 若 SF = 0 OF = 0 则表示无负数,无溢出,则代表ax > bx 若 SF = 1 OF = 0 则表示有负数,无溢出 则 ax < bx 若 SF = 0 OF = 1 则表示无负数 有溢出 则 ax<bx 若 SF = 1 OF = 1 则表示有负数 有溢出 则 ax > bx
3.4 状态位条件转移指令
我们之前有使用过jcxz 转移指令,其通过判断cx寄存器的值来判断是否需要转移,而汇编语言中还存在一些基于Flag寄存器状态位来判断是否转移的指令。
指令 | 含义 | 相关状态位的值 |
---|
je | 等于则转移 | zf = 1 | jne | 不等于则转移 | zf = 0 | jb | 低于则转移 | cf = 1 | jnb | 不低于则转移 | cf = 0 | ja | 高于则转移 | cf = 0 或 zf=0 | jna | 不高于则转移 | cf =1 或 zf =1 |
其使用方式与jcxz 指令无异,比如我们要判断如果 ax = bx 则cx = 1 反之则为 2
assume cs:code
code segment
main : mov ax,2
mov bx,1
mov cx,0
cmp ax,bx
je equals
mov cx,2
equals : mov cx,1
code ends
end main
3.5 数据传送指令
数据传送指令有很多种,比如movsb ,movsw 等,它们的使用场景大体相同,我们以movsb 举例:
movsb 是一个串传送指令,起作用相当于:
- es16 + di = ds16 + si
- 如果df = 0 则 di+1 , si+1
- 如果df=1 则 di-1 ,si-1
我们用简单易懂的方式翻译一下:当我们调用movsb 的时候,我们会将ds:si的数据复制到es:di 。同理的movsw 表示为两个字长,因此di及si的递增递减应为2。
这里再提一下rep 指令,起作用与loop 类似,是根据cx寄存器的值重复执行其后续的串转移指令。
3.6 pushf和popf
pushf 的含义就是push flag,它的作用就是将标志寄存器的值压栈,与之相对的就是popf ,其作用就是将标志寄存器的值弹栈。两个指令配合使用是为了可以获取当前flag寄存器的值以作判断。比如:
assume cs:code
code segment
main:
mov ax,2
mov bx,1
sub ax,bx
pushf
pop ax
code ends
end main
结语
今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。
码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦
|