第十一章 标志寄存器

时间:2021-06-13 01:26:12

标志寄存器同普通的寄存器一样有16位,它的主要作用就是提供一些程序的状态字段,简称程序状态字

ZF(零状态寄存器)

当使用 运算指令(比如add、sub、mul等)时,可能会修改ZF位;而传送指令(比如mov、pop、push等)就不会修改ZF位。当执行了运算指令后结果为 0ZF=1,如果结果不为 0ZF=0

    sub al, al ;执行完后ZF=1
    add al, 1  ;执行完后ZF=0

PF(奇偶标志位)

该位表示一个数据的所有比特位中 1的个数 是否为偶数,如果 1的个数 为偶数,那么PF = 1;反之则为0

    sub al, al ; PF = 1
    add al, 3  ; PF = 0

SF(符号标志位)

该位表示该数据是否带有符号,即是否是负数。而该寄存器仅当开发人员在做有符号运算时才生效,这句话怎么理解?众所周知,计算机其实就是一堆或非门组成的,其本质上就是一堆01组成的无符号数(正数),而人们为了表示负数,将bit位的最高位作为符号位,也就是说负数是人为加上去的,一堆01可以看成是无符号数,也能看成是有符号数。而程序是不知道人类如何思考的,所以索性使用SF寄存器,如果最高为是1,我给你把SF设置为1,你用不用是你的事。

注意,这里的mul、div指令均不会对SF产生影响。

    mov al 0000 0001B ;可以看作无符号数 1,也能看作有符号数 1
    mov al 1000 0001B ;可以看作是无符号数 129,也能看做是有符号数 -127 

CF(进位标志位)

进位标志位在进行无符号运算时,记录了运算结果的最高有效位向更高位的进位值(或借位值)。换句话说就是当两个数相加,不够保存时(超出当前最大位数)CF=1,而当两个数相减时,
第十一章 标志寄存器

    ; 加法进位
    mov al, 98H 
    add al, al  ; 98H + 98H = 130H,其位数已经超过8位,产生了进位,故CF=1
    ; 减法借位
    mov al, 97H
    sub al, 98H ; 97H - 98H = 0FFH,由于97H不够减98H,故需要向更高一位借位(借位后相当于197H - 98H = 0FFH),此时CF=1

OF(溢出标志位)

溢出标志位仅对有符号运算时才有作用,当执行的运算超过了机器所能表示的位数(有符号)时,该标志位就会被置为1。

    ;执行后,al=12F,两个负数相加本来应该等于负数,但由于寄存器只有8位,所以最终al=2F,变为正数,属于溢出
    mov al, 97H
    add al, 98H 

    ;执行后,al=69,此次运算,一个稍大一点的正数加一个负数,等于一个正数,没有产生溢出,故OF!=1
    mov al, 0F1H
    add al, 78H 

我们要注意一件事,在讨论OF或CF时,请简单考虑各自的场景,不要混淆!比如在判断OF时,FF + FF这种运算在无符号运算看来 已经超出了最大位数了(假设在8位下相加),所以 CF = 1;而在有符号运算看来 -1 + -1 = -2 并没有发生溢出,所以 OF = 0

再看个例子,50H + 60H无符号数运算看来 并没有超过最大位数,所以 CF = 0;而在有符号运算看来 两个正数相加变成了负数,发生了溢出,所以 OF = 1

简而言之,溢出标志位只需要考虑 两个数相加后,逻辑上是否符合正常思想(就是正数+正数=正数;负数+负数=负数的正常逻辑),如果违反了(比如正数+正数=负数,负数+负数=正数),那么就一定会产生溢出了,那么有同学可能就会问了 负数+正数 的情况呢??请问,一个固定位数的寄存器里面,最大的负数能有多少?更何况是加上一个正数呢!所以负数+正数,这辈子都不可能产生溢出!。

adc(带上进位值的加法运算)

当我们执行两个数的相加时,可能会产生进位值,如果想要把这个进位值用在下一次运算中,该如何做呢?汇编语言就提供了一个adc指令(这里的c可以理解为carry,即进位),该指令的使用方式和add指令相同,均是adc/add 操作对象1,操作对象2,而在内部却有一些不同:adc指令在进行加法运算的时候会额外加上CF的值,相当于 a + b + CF

该指令主要运用在计算 更大的数据 ,比如计算 10F000H + 202000H,我们会这样做:

  1. 低16位先相加—— F000H + 2000H = 11000H,由于16位寄存器只能保存16位,故AX(因为ADD指令执行16位加法时默认保存在AX中)中保存了1000H,CF设置为1
  2. 随后进行高位相加—— 10H + 20H = 30H
  3. 最后因为低16位产生了进位值,所以高位结果还需要加1,高位最终结果为 31H
  4. 所以最后的结果为 311000H

这里的2、3步结合在一起就是 adc指令的执行过程

sbb(带上借位值得减法运算)

sbb指令和adc指令得作用差不多,总归是用于运算更大的数据。sbb指令的使用形式和 sub指令 一样,不过也是在内部执行时多减去一个 CF

这里放个例子,计算 012333H - 013333H,我们会这样做:

  1. 低16位先相减—— 2333H - 3333H = F000H,由于2333H 不够 3333H减,所以2333H会找更高位借一位(此时2333H -> 12333H),故此时CF = 1
  2. 进行高位相减—— 01H - 01H = 0H ? 并不是,低16位借了一位,被减数已经不是那个被减数了,所以此时的运算应该是 01H - 01H - 01H = -1,也就是FFFF
  3. 所以最终的高位结果是 FFFFH(这里F的个数是基于当前寄存器的位数,如果寄存器是16位,那么就是FFFF,如果是8位就是FF),合在一起就是 FFFF F000H

cmp

cmp的本质就是做一个减法(此减法没有副作用),然后改变标志位。由于有符号数和无符号数的减法产生的结果各不相同(因为有符号数会产生溢出),所以cmp指令也分无符号数有符号数

cmp的无符号数判断

第十一章 标志寄存器

以上是书上总结的规律,但是我觉得ax >= bx有些不妥,既然ax <= bx,能推出CF = 1或 ZF = 1,那么ax >= bx应该推出CF=0或ZF=1

cmp的有符号数判断

相比无符号cmp,有符号的cmp需要讨论一下 大于/小于 的情况,因为这两种情况可能会产生溢出,比如 (al) < (bl)时,080H - 070H(负数-正数 => 负数+负数) 以及 080H - 0FEH(负数-负数 => 负数+正数),在这两种情况下,他们的标志寄存器的结果不尽相同,下面对他们进行逐个分析

  1. 如果(ax) < (bx)
    • 如果(ax) < 0, (bx) < 0,(ax) - (bx)必然不可能溢出时,结果为负数,此时OF=0,SF=1
      • 例子:080H - 0FFH = 81H
    • 如果(ax) < 0, (bx) > 0,(ax) - (bx)溢出时,结果为正数,此时OF=1,SF=0
      • 例子:080H - 01H = 7FH
    • 如果(ax) < 0, (bx) > 0,(ax) - (bx)不溢出时,结果为负数,此时OF=0,SF=1
      • 例子:0FFH - 0FH = 0F0H,
  2. 如果(ax) > (bx)
    • 如果(ax) > 0, (bx) > 0,(ax)- (bx)必然不会溢出,结果为正数,此时OF=0,SF=0
      • 例子:7FH - 1H = 7EH
    • 如果(ax) > 0, (bx) < 0, (ax) - (bx) 产生溢出时,结果为负数,此时OF=1,SF=1
      • 例子:7FH - FFH = 80H
    • 如果(ax) > 0, (bx) < 0, (ax) - (bx) 不产生溢出时,结果为正数,此时OF=0,SF=0
      • 例子:0AH - FFH = 0BH

总而言之,正常情况下只要通过SF是否等于1来判断是(ax)<(bx)还是(ax)>(bx),但是遇到OF=1,还得对SF取反,最后才能使用SF进行判断,比如判断 (ax) < (bx) 的依据是 (OF == 0 && SF == 1 ) || (OF == 1 && SF == 0);(ax) > (bx) 的依据是 (OF == 0 && SF == 0 ) || (OF == 1 && SF == 1)

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

上面一节介绍了cmp指令的一些功能,本节是要把标志寄存器里面的各位利用起来。

该节主要介绍一些根据标志寄存器的标志位进行跳转的指令,这些指令仅用于无符号数:

  1. je >>> 该指令表示等于则跳转(ax = bx, ZF=1)
  2. jne >>> 不等于则跳转(ax != bx, ZF=0)
  3. jb >>> 低于则跳转(ax < bx, CF=1)
  4. jnb >>> 不低于则跳转(ax >= bx, CF=0)
  5. ja >>> 高于则跳转(ax > bx, CF = 0 && ZF = 0)
  6. jna >>> 不高于则跳转(ax <= bx, CF = 1 || ZF = 0)

这里的指令只会取寄存器里面指定位的数据,然后判断是否跳转,这里就不多介绍了。

DF标志和字串传送指令

在介绍DF标志前,先讲讲字串传送指令,该指令按操作数大小分为两种:

  1. movsb,该指令一次传输一个字节
  2. movsw,该指令一次传输一个字

它们的操作过程用汇编语言来描述就像如下所示:

  1. mov byte ptr es:[di], ds:[si] 或者 mov word ptr es:[di], ds:[si]
  2. add si, X | sub si, X(根据传输的大小,X会有所不同:byte下,X=1;word下,X=2)
  3. add di, X | sub di, X(根据传输的大小,X会有所不同:byte下,X=1;word下,X=2)

而决定si、di是递增还是递减,是由标志寄存器的第10位(DF位)来决定的,如果DF=1,那么就执行增加,比如 add si, 1 或者 add si, 2;如果DF = 0,那么就执行减少

pushf和popf

pushf用于把标志寄存器压入栈中;popf用于把标志寄存器弹出栈,用法和pushpop差不多

标志寄存器在DEBUG中的显示

值为1 值为0
OF OV NV
SF NG PL
ZF ZR NZ
PF PE PO
CF CY NC
DF DN UP

课后检测点

第十一章 检测点