标志寄存器

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

  (1)用来存储相关指令的某些执行结果;

  (2)用来为 CPU 执行相关指令提供行为依据;

  (3)用来控制 CPU 的相关工作方式。

  这种特殊的寄存器在 8086CPU 中,被称为标志寄存器。8086CPU 的标志寄存器有 16 位,其中存储的信息通常被称为程序状态字(PSW)。我们已经使用过 8086CPU 的 ax、bx、cx、dx、si、di、bp、sp、IP、cs、ss、ds、es 等 13 个寄存器了,本章中的标志寄存器(以下称为 flag)使我们要学习的最后一个寄存器。

  flag 和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而 flag 寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。

  

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

 

11.1 ZF 标志

  flag 的第 6 位是 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 等,它们大都是传送指令。在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的哪些标志位造成影响。

 

11.2 PF 标志

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

  比如,指令:

  mov al,1

  add al,10

  执行后,结果为 00001011B,其中有 3(奇数)个 1,则 pf=0;

  mov al,1

  or al,2

  执行后,结果为 00000011B,其中有 2(偶数)个 1,则 pf=1。

 

11.3 SF 标志

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

  计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。比如:

  00000001B,可以看作无符号数 1,或有符号数 +1;

  10000001B,可以看作为无符号数 129,也可以看作有符号数 -127。

  也就是说,对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。比如:

  mov al,10000001B

  add al,1

  结果:(al)=10000001B。

 

  可以将 add 指令进行的运算当作无符号数的运算,那么 add 指令相当于计算 129+1,结果为 130(10000010B);也可以将 add 指令进行的运算当作有符号数的运算,那么 add 指令相当于计算 -127+1,结果为 -126(10000010B)。

  不管我们如何看待,CPU 在执行 add 指令的时候,就已经包含了两种含义,也将得到用同一种信息来记录的两种结果。关键在于我们的程序需要哪一种结果。

  SF 标志,就是 CPU 对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF 的值则没有意义,虽然相关的指令影响了它的值。

  这也就是说,CPU 在执行 add 等指令的时候,是必然要影响到 SF 标志位的值的。至于我们需不需要这种影响,那就看我们如何看待指令所进行的运算了。

  

11.4 CF 标志

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

  对于位数为 N 的无符号数来说,其对应的二进制信息的最高位,即第 N-1 位,就是它的最高有效位,而假想存在的第 N 位,就是相对于最高有效位的更高位。

  

  我们知道,当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如,下面的指令:

  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 记录了向更高位的借位值。

 

11.5 OF 标志

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

  那么,什么是机器所能表示的范围呢?

  比如说,指令运算的结果用 8 位寄存器或内存单元来存放,对于 8 位的有符号数据,机器所能表示的范围是 -128~127,同理对于 16 位有符号数据,机器所能表示的范围是 -32768~32767。

  如果运算结果超出了机器所能表达的范围,将产生溢出。

  注意,这里所讲的溢出,只是对有符号数运算而言。含义:我们把运算数当作有符号数的时候,如果超出了最大范围,则把 OF 设置为 1。

 

  一定要注意 CF 和 OF 的区别:CF 是对无符号数运算有意义的标志位,而 OF 是对有符号数运算有意义的标志位。另外需要注意的是,一个数可能既是有符号数,也可以是无符号数。也就是说,运算的时候可能会同时影响 CF 和 OF。而对于我们有没有意义倒说不定。前面我们讲过,CPU 在执行 add 等指令的时候,就包含了两种含义:无符号数运算和有符号数运算;对于有符号数运算,CPU 用 CF 位来记录是否产生了进位;对于无符号数运算,CPU 用 OF 位来记录是否产生了溢出。如下所示:

  mov al,0f0h

  add al,88h

  add 指令执行后:CF=1,OF=1。对于无符号数运算,0f0h+88h 有进位,CF=1;对于有符号数运算,0f0h+88h发生溢出,OF=1。

  mov al,0f0h

  add al,78h

  add 指令执行后:CF=1,OF=0。对于无符号运算,0f0h+78h 有进位,CF=1;对于有符号数运算,0f0h+78h 不发生溢出,OF=0。

 

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

 

11.6 adc 指令

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

   指令格式:adc 操作对象 1, 操作对象 2

  功能:操作对象 1 = 操作对象 1 + 操作对象 2 + CF

  比如指令 adc ax,bx,实现的功能是: (ax) = (ax) + (bx) + CF

  例:

  mov ax,2

  mov bx,1

  sub bx,ax

  adc ax,1

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

  为什么要提供这样一条指令呢?

  大数的加法可以分为两步来计算:①低位相加;②高位相加再加上低位相加产生的进位值。

  

  看一个例子,计算 1EF000H + 201000H,结果放在ax(高 16 位)和bx(低 16 位)中。

  因为两个数据的位数都大于 16,用 add 指令无法进行计算。我们将计算分为两步进行,先将低 16 位相加,然后将高 16 位和进位值相加。程序如下:

  mov ax,001EH

  mov bx,0F000H

  add bx,1000H

  adc ax,0020H

  adc 指令执行后,也可能产生进位值,所以也会对 CF 位进行设置。由于有这样的功能,我们就可以对任意大的数据进行加法运算。

 

  编写一个子程序,对两个 128 位数据进行相加。

  名称:add128

  功能:两个 128 位数据进行相加。

  参数:ds:si 指向存储第一个数的内存空间,因数据为 128 位,所以需要 8 个字单元,由低地址单元到高地址单元依次存放 128 位由低到高的各个字。运算结果存储在第一个数的存储空间中。

  ds:di 指向存储第二个数的内存空间。

  程序如下:

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

  

11.7 sbb 指令

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

  指令格式:sbb 操作对象1,操作对象2

  功能:操作对象1 = 操作对象1 - 操作对象2 - CF

  比如指令 sbb ax,bx 实现的功能是:(ax) = (ax) - (bx) -CF

  sbb 指令执行后,将对 CF 进行设置。利用 sbb 指令可以对任意大的数据进行减法运算。

  sbb 和 adc 是基于同样的思想设计的两条支流,在应用思路上和 adc 类似。

 

11.8 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。

  其实,我们通过 cmp 指令执行后,相关标志位的值就可以看出比较的结果。

  cmp ax,bx

  如果 (ax)=(bx) 则 (ax)-(bx)=0,所以:zf=1;

  如果 (ax)≠(bx) 则 (ax)-(bx)≠0,所以:zf=0;

  如果 (ax)<(bx) 则 (ax)-(bx) 将产生借位,所以:cf=1,zf=0;

  如果 (ax)≥(bx) 则 (ax)-(bx) 不必借位,所以:cf=0;

  如果 (ax)>(bx) 则 (ax)-(bx) 既不必借位,结果又不为 0,所以:cf=0 并且 zf=0;

  如果 (ax)≤(bx) 则 (ax)-(bx) 既可能借位,结果可能为 0,所以:cf=1 或 zf=1。

  现在我们可以看出比较指令的设计思路,即:通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较的结果。

  反过来看上面的例子。

  指令 cmp ax,bx 的逻辑含义是比较 ax 和 bx 中的值,如果执行后:

  zf=1,说明 (ax)=(bx)

  zf=0,说明 (ax)≠(bx)

  cf=1,说明 (ax)<(bx)

  cf=0,说明 (ax)≥(bx)

  cf=0 并且 zf=0,说明 (ax)>(bx)

  cf=1 或 zf=1,说明 (ax)≤(bx)

  同 add、sub 指令一样,CPU 在执行 cmp 指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算。所以利用 cmp 指令可以对无符号数进行比较,也可以对有符号数进行比较。上面讲的是用 cmp 进行无符号数进行比较时,相关标志位对比较结果的记录。

 

  下面我们以 cmp ah,bh 为例,总结一下 CPU 执行 cmp 指令后,sf 和 of 的值是如何来说明比较的结果的。

  (1) 如果 sf=1,而 of=0

  of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;

  因 sf=1,实际结果为负,所以逻辑上真正的结果为负,所以 (ax)<(bx)。

  (2) 如果 sf=1,而 of=1

  of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负

  因 sf=1,实际结果为负。

  实际结果为负,而又有溢出,这说明是由于溢出导致了实际结果为负,简单分析一下,就可以看出,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。(比如 130 二进制为 1000 0010 B,溢出导致结果为负数,需要注意的是,溢出和进位不一样)

  这样,sf=1,of=1,说明了 (ah)>(bh)。

  例如:00000000B - 10000000B

  (3) 如果 sf=0,而 of=1

  of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;

  因 sf=0,实际结果非负。而 of=1 说明有溢出,则结果非 0,所以,实际结果为正。

  实际结果为正,而又有一处,这说明是由于溢出导致了实际结果非负,简单分析一下,就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。(比如 -130 二进制为 0111 1110 B,溢出导致结果为正)

  这样,sf=0,of=1,说明了 (ah)<(bh)。

  sf=0,of=1,说明最高位相同,并且有溢出,这样说明了 (ax) 必然小于 (bx)。

  (4) 如果 sf=0,而 of=0

  of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;

  因 sf=0,实际结果非负,所以逻辑上真正的结果非负,所以 (ah)≥(bh)。

 

  另外一种理解方式是,其实减法也可以转换为加法,只要把第二个数取反(十进制意义的取反),

  比如:

  溢出导致结果为正,实际结果必然为负数(cmp 实际上等于两个负数相加)。

  mov ah,08Ah

  mov bh,070h

  cmp ah,bh

  结果 sf=0,of=1,因为上面的 (ah)-(bh) 可以看作 1000 1010B + 1001 0000B,这里最高位都为 1,负数相加产生溢出,实际结果为负数,但是低 8 位是正数,所以 sf=0。

 

  另外一个例子:

  溢出导致

  mov ah,22H

  mov bh,0A0H

  cmp ah,bh

  结果 sf=1,of=1,上面的代码可以看作 0010 0010B + 0110 0000B,这里两个正数相加变负数,也产生了溢出,所以 of=1,sf=1。

 

  溢出导致结果为负数 => 最高位 其实不是符号位,所以我们应该把结果当作正数看待;溢出导致结果为正数 => mov ah,0; add al,bl; 最高位本来是1(负数),但是相加之后最高位进位到更高位,这就超出了负数所能表达的最大范围(-128) ,实际结果为 - 2ˆ8 + 低8位正数。

  好像上面的说法都不太好理解,也许可以简单看作,cmp 的时候,把第二个数转换为正数;两个数最高为 1 的时候,为两个负数相加,但是会溢出,结果变正数,但实际结果应该是负数;两个数最高位为 0 的时候,如果产生溢出,并且得到结果是负数,那么说明,超过了数字正数的最大范围(8 位为 127),所以实际结果应该是正数。

 

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

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

  比如,jcxz 就是一个条件转移指令,它可以检测 cx 中的数值,如果 (cx)=0,就修改 IP,否则什么也不做。所有条件转移指令的转移位移都是 [-128~127]。

  除了 jcxz 之外,CPU 还提供了其他条件转移指令,大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改 IP。它们检测的是哪些标志位呢?就是被 cmp 指令影响的那些,表示比较结果的标志位。这些条件转移指令通常都和 cmp 相配合使用,就好像 call 和 ret 指令通常相配合使用一样。

  因为 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 指令进行无符号数比较的时候,记录比较结果的标志位。比如 je,检测 zf 位,当 zf=1 的时候进行转移,如果在 je 前面使用了 cmp 指令,那么 je 对 zf 的检测,实际上就是间接地检测 cmp 的比较结果是否为两数相等。

 

11.10 DF 标志和串传送指令

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

  df=0 每次操作后 si、di 递增;

  df=1 每次操作后 si、di 递减。

  我们来看下面的一个串传送指令。

  格式:movsb

  功能:执行 movsb 指令相当于进行下面几步操作。

  (1)((es)*16+(di))=((ds)*16 + (si))

  (2)如果 df=0 则:

    (si)=(si)+1

    (di)=(di)+1

  如果 df=1 则:

    (si)=(si)-1

    (di)=(di)-1

  用汇编语法描述 movsb 的功能如下。

  mov es:[di], byte ptr ds:[si]  ;8086 并不支持这样的指令,这里只是个描述

  如果 df=0:

    inc si

    inc di

  如果 df=1:

    dec si

    dec di

  可以看出,movsb 的功能是将 ds:si 指向的内存单元中的字节送入 es:si 中,然后根据标志寄存器 df 位的值,将 si 和 di 递增或递减。

  

  当然,也可以传送一个字,指令如下。

  格式:movsw

  movsw 的功能是将 ds:si 指向的内存字单元中的字送入 ds:di 中,然后根据标志寄存器 df 位的值,将 si 和 di 递增 2 或递减 2。

  用汇编语法描述 movsw 的功能如下。

  mov es:[di], word ptr ds:[si]  ;8086 并不支持这样的指令,这里只是个描述

  如果 df=0:

    add si,2

    add di,2

  如果 df=1:

    sub si,2

    sub di,2

  movsb 和 movsw 进行的是串传送操作中的一个步骤,一般来说,movsb 和 movsw 都和 rep 配合使用,格式如下:

  rep movsb

  用汇编语法来描述 rep movsb 的功能就是:

  s: movsb

    loop s

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

  同样的道理,也可以这样使用 movsw:rep movsw。

  相当于:

  s: movsw

    loops

  

  由于 flag 的 df 位决定着串传送指令执行后,si 和 di 改变的方向,所以 CPU 应该提供相应的指令来对 df 位进行设置,从而使程序员能够决定传送的方向。

  8086CPU 提供下面两条指令对 df 指令进行设置。

  cld 指令:将标志寄存器的 df 位置 0

  std 指令:将标志寄存器的 df 位置 1

 

11.11 pushf 和 popf

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

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

 

11.12 标志寄存器在 Debug 中的表示

  在 Debug 中,标志寄存器是按照有意义的各个标志位单独表示的。

  

 

posted @ 2017-11-05 09:06  佚名000  阅读(1291)  评论(0)    收藏  举报