flag
flag和之前所学的寄存器都不一样,它是8086CPU中的标志寄存器。它具有以下三种功能:
- 用来存储相关指令的某些执行结果
- 用来为CPU执行相关指令提供行为依据
- 用来控制CPU的相关工作方式
这个标志寄存器有16位,其中存储的数据我们称之为程序状态字。和其他寄存器不同,其他寄存器整个包含一个信息,而标志寄存器每一位都包含一个信息。
这是flag寄存器的各位示意图。它的 1 3 5 12 13 14 15位没有信息,没有任何意义。而其他标志位就都有含义
ZF标志
zf标志位就是记录一个运算的结果是否为0的。如果那个运算的结果为0,它就要记录下来,不为0就不记。那么它是怎么来记录的呢?当一个运算的结果为0时,标志位就会变成 1 ,当不为0时,标志位就是0。
比如 mov ax,1 sub ax,1 。这个运算结果为0,标志位zf就会变成 1 。 mov ax,1 or ax,0 。这个运算结果为1 ,标志位zf 就为0
并且,只有像 add,sub,and,or,div,mul,inc这种运算指令的结果才会对寄存器 zf 造成影响,像mov ,push,pop ,这样的传送指令一般不会造成影响。
PF标志
flag的第二位,奇偶标志位。它用来记录一个运算结果其所有比特位中,1的个数是否为偶数。如果是偶数那么它就记录下来,这个标志位变为1,如果是奇数,就不记录,就是0.
比如 mov ax,1 add ax,2 结果为3 二进制为 00000011 1的个数为偶数,PF标志位变为1。mov ax,1 and ax, 1 结果为1,奇数 ,PF标志位变为0。
SF标志位
它用来记录一个运算的结果是否为负,如果为负就记录下来,这个标志位变为1,如果不为负,就不记录,标志位就是0。
因为在计算机的计算中,负数用补码表示。所以有时候一个数可以当成有符号的,也可以当成无符号的来计算。
比如 10000001B 它可以是无符号的 129 也可以是有符号的 -127 (换成补码算)。
所以,mov ax,1 add ax,10000001B 如果进行的是有符号的运算,那么SF的值就为1,结果就为负。如果进行的是无符号的运算,那么SF就为0,结果为正。
CF标志位
这个标志位记录了计算时,向更高位的借位值或者进位值。有时候我们在进行计算时,可能会出现超出位数的情况,比如在一个8位寄存器进行加法运算,但是结果却超出8位,这时要向更高位进一位,进的那一位就储存在这个CF标志位中。或者有时候我们做减法运算,可能位数不够我们需要向更高位借一位。借的那一位也储存在CF标志位中。inc 指令不对CF造成影响。
比如 mov ax,11111111B add ax,1 结果为0,CF标志位为1 。比如 mov ax,1 sub ax,2 结果为9 CF标志位为1 ,向更高位借了1.
OF标志位
这个标志位是用来记录有符号的运算是否发生溢出。一个8位二进制数,当为无符号时,它可以表示从0到255,而当为有符号时,因为第一位是符号位,所以它可以表示 -128~127 。所以有时候进行有符号的运算时会发生溢出。而当计算机进行的是有符号的运算时,一些计算结果可能会让我们感到很奇怪。比如 mov ax,0f0 add ax, 088H ,因 f0 是-16的补码 , 088H 是 -120 的补码(负数在计算机中都是以补码形式存在)。所以如果我们是用add进行的是有符号的运算的话,结果应该是 -136 。但是超过了-128,发生了溢出。所以得到一个结果:78H,表示有符号数120 。这是一个错误结果。因为作为有符号数,-136在8位寄存器中存放不下。
因为溢出时结果会发生错误,所以标志寄存器中有一个标志位要用来记录是否发生溢出。就是OF寄存位,它只记录有符号位是否发生溢出,如果计算发生溢出,这个标志位的值就为1,没有就为0。
注意:CF标志位和OF标志位的区别。CF标志位是记录无符号运算的进位值和借位值。OF标志位是记录有符号运算是否发生溢出。它们之间没有任何关系。
adc指令:
adc指令也叫带进位加法指令。用法 adc 操作对象1,操作对象2 。这个指令将会执行 操作对象1+操作对象2+CF的值。出现这个指令的原因是过我们提供任意位数加法的方法。当我们要计算的数据的位数超过了寄存器的位数时,要产生进位,而这会造成数据的缺失。我们可以看一下加法操作产生进位时是如何进行计算的:
我们可以看到,其实就是高位相加再加上低位相加产生的进位。所以我们的adc指令就派上了用场,我们可以把一个很大的数字分成几部分来计算,先低位相加,然后高位再利用 adc 指令相加,然后更高位再用 adc 指令相加,一直到所有位数都加完。
我们可以写一个计算两个128位数据相加的子程序,以后也可以照这种模板来写。
sbb指令:
sbb指令也叫带借位减法指令。用法: sbb 操作对象1,操作对象2 。这个指令将会执行 操作对象1-操作对象2-CF的值。因为减法操作时,当低位的数不够大时就要向高位借位,而有时候寄存器的位数不够用,我们就可以用到和上面一样的方法。
cmp指令:
这是一个比较指令,也是一个减法指令,但是它只是做一下减法运算,并不储存结果。用法 cmp 操作对象1,操作对象2.
它将会将操作对象1减去操作对象2。这个指令的用处就是它会对标志寄存器的各个位产生影响,然后其他指令可以通过观察这些影响来判断那两个数谁大谁小。
比如 cmp ax,ax 结果为0,它对每个标志位造成的影响: 结果为0:zf=0,结果1的位数为偶数: pf=1,结果非负 : sf=0 结果没有溢出 :of=0 没有借位:cf=0 。
所以,当我们进行无符号运算时,可以根据 zf,cf来判断两个数谁大谁小。
比如 cmp ax,bx
如果 zf =0 那么说明 ax=bx,zf不等于0,则两个数不相等
如果 cf = 0 ,则没有发生借位,此时 ax>=bx ,然后再看 zf 的值来判断, zf =0则 ax=bx ,zf不等于0 则ax>bx
如果 cf = 1,则发生了借位,ax<bx。
而进行有符号运算时,情况就可能发生变化,因为有符号运算会有溢出问题,所以我们不能只看,zf 和 sf 的值,我们还要看 of 的值。
我们可以通过计算知道,两个有符号的数相减,如果发生溢出,并且它的逻辑上应该得到的结果为正数,那么它在计算机中得到的实际结果将会是负数。反过来,如果逻辑上的应该得到的结果为负数,那么它在计算机中得到的实际结果将会是正数。
如果没有发生溢出,那么一切照常。
所以我们可以通过观察 sf,of 的值来判断:
cmp ax,bx
如果 zf=0 则 ax=bx ,如果不等于,则二者不相等,则:
如果 sf = 0 ,那么计算机得到的实际结果为正,然后我们再看 of 的值,如果 of =0 那么说明没有发生溢出,则逻辑上应该得到的结果也为正,那么说明 ax>bx,如果 of =1 ,则说明发生了溢出,则逻辑上应该得到的结果为负,则 ax < bx。
当 sf = 1 时也用同样的方法来判断。
检测比较结果的条件转移指令:
条件转移指令就是满足条件它才执行,不满足就不执行,比如 jcxz指令,只有当CX中的值为0时它才转移,不为0就什么都不做。但其实还有很多其他的条件转移指令,只是它们的条件是根据标志寄存器中的值来判断的,所以我们一般用 cmp指令和这些条件转移指令配套使用,这两种指令配套使用就有点像c++中的 IF 语句。我们可以看一下这些条件转移指令:
我们配套使用时,看似好像这些条件转移指令是根据 cmp 指令来执行的,实际上这些条件转移指令都是根据标志寄存器中的位的值来执行的, cmp 指令只是影响了这些标志位。上面这张图是无符号计算时,所依据的标志位。当进行有符号计算时,转移指令不变,变的只是依据的标志寄存位。
注意:这些指令是依据标志寄存器中的值来判断是否需要进行转移,而不是根据指令 cmp ,所以就算这些条件转移指令前面没有 cmp 指令,这些指令也可能进行。
DF标志和串传送指令:
df 标志位是用来给 指令 movsb 和 movsw 对 寄存器 si,di 中的值进行怎样得计算来提供依据的。首先我们介绍一下 movsb
这个指令的作用就是 先将 DS:SI中的值复制到 ES:DI中,然后根据 df 中的值来对 si 和 di 进行加一或减一计算,对内存单元的操作为字节操作,传送的是一个字节。当 df 中的值为0,它就将 di,si 的值都加一,当 df 中的值为1 ,它就将 di,si 中的值减一。
而 movsw 的道理和movsb 是一样的,只是它进行的是字操作,每次传送一个字。
这两个指令就相当于:
那么既然这两个指令是根据 df 中的值来进行操作的,那么我们肯定就有能改变 df 的指令,要不然太被动。
cld 指令 :将 df 位置为0
std 指令 :将 df 位置为1
我们可能会感觉到这个movsb,movsw 指令有点像c++中的循环指令,但是它没有循环,于是我们又多了一个指令来让它能循环。
rep 指令 rep 指令一般和 movsb或movsw 指令配合使用,放在这两个指令的前面。它也是根据 CX中的值来决定是否要继续执行的。相当于:
同样也是当 CX中的值为0时停止循环。
它的名字叫串传送指令,是因为它的循环只能将那个规定的内存单元的值传送到另一个规定内存单元中。movsb或movsw 只能进行循环或者说串传送中的一次操作,而加了 rep 后就可以完成整个传送。所以我们要进行传送就要提供一些数据:
先设定 DS:SI,ES:DI,设定传送的位置
再设定 CX 设定传送的长度
再设定 DF 设定传送的方向(是越来越往大的走还是小的)(使用 cld或std)
pushf和popf:
pushf指令的作用是将标志寄存器中的值压入栈,而popf指令的作用是将栈中的数据弹入标志寄存器。它们为直接访问寄存器提供了方式。
标志寄存器在debug中的表示:
在debug中我们可以看到标志寄存器中各个位的值
右边下面的那些符号就是对应的标志寄存器中的一些位,它们的对应关系为:
这些符号并不是一直是这个样子,只是这些标志位为1或0时,它们会有个特定的符号,下面是它们的不同值时的关系:
通过上面的对应表我们可以知道此时寄存位的值 ,of=0,df=0,sf=0,zf=0,pf=0,cf=0.
如果我们做一下计算,看看这些寄存位的值是否会发生变化。
我们这个计算的结果为0,按照规则,zf 和 pf 中的值都要变为1,其他都不变,因为这个结果为0,结果中1的个数为0(偶数),没有借位进位(cf不变),结果不是负数(sf不变),这是无符号的计算,没有溢出(of不变)。对照上表可以发现 的确只改变了ZR以及PE