4.2.6 加法和减法影响的标志

 

在执行算术指令时,我们通常要了解运算结果的一些特征,比如:结果为负还是为正,或者是否为零?结果是否太大或太小以至于目标操作数无法容纳?对这些特征的分析有助于侦测运算错误。有些运算错误可能会导致难以捉摸的程序行为。状态标志可用于检查算术运算的输出结果是否正确,还用于激活分支指令——构成程序逻辑的基本工具。下面是一些状态标志的简要描述,在后面还会详细解释:

  • 进位标志用于表示无符号整数运算是否发生了溢出。例如,假设指令的目的操作数是8位的,但该指令产生的结果却大于二进制数11111111,那么进位标志就会置位。
  • 溢出标志用于表示有符号整数运算是否发生了溢出。例如,假设指令的目的操作数是16位的,但该指令产生的结果却小于十进制数-32768,那么溢出标志就会置位。
  • 零标志用于表示运算结果是否为零。例如,一个操作数减去一个同值的操作数,那么零标志就会置位。
  • 符号标志用于表示运算结果是否为负如果运算结果的最高有效位被置位,那么符号标志就会置位。
  • 奇偶标志用于表示目的操作数的最低有效字节内1的个数是否为偶数。
  • 辅助进位标志在运算结果(存储于目的操作数中)的最低有效字节的第三位向高位产生进位1时置位。

可以在程序中调用本书附带链接库中的DumpRegs过程显示CPU的状态标志值。

无符号运算:零标志、进位标志和辅助进位标志

算术运算的结果为零时零标志被置位,下面的例子在注释中给出了在执行完SUB,INC和DEC指令后目的操作数和零标志的状态:

mov ecx,1
sub ecx,1        ;ECX = 0,ZF = 1
mov eax,0FFFFFFFFh
inc eax          ;EAX = 0,ZF = 1
inc eax          ;EAX = 1,ZF = 0
dec eax          ;EAX = 0,ZF = 1

加法和进位标志:如果分别考虑加法和减法两种情况,对进位标志的操作是最容易观解的两个无符号整数相加,进位标志的值就是运算结果(保存在目的操作数中)最高有效位(MSB)向高位的进位值。这观地看,当和超过了目的操作数的大小时CF等于1。在下面的例子中,ADD设置了进位标志,这是由于和(100h)对AL寄存器而言太大了,无法存放在AL寄存器中:

mov al,0FFh
add al,1        ;AL = 00,CF = 1

下图在数据位的层次显示了当0FFh加1时究竟发生了什么,其中AL的最高有效位向高位的进位值被复制到进位标志中:

另一方面,如果我们向AX中的00FFh加1的话,和便可容纳于16位的值中,相应地,进位标志被清零:

mov ax,00FFh
add ax,1        ;AX = 0100h,CF = 0

如果AX寄存器中的FFFFh加1,那么AX的最高位就会产生进位:

mov ax,0FFFFh
add ax,1        ;AX = 0000h,CF = 1

减法和进位标志:在进行减法运算时,如果一个较小的无符号整数减去一个较大的无符号整数,进位标志也会被置位。从硬件的角度考虑减法对进位标志的影响是最容易理解的。让我们假设在某一时刻,CPU能够对一个无符号正整数求补得到其相反数:

  1. 对源操作数(减数)求反再与目的操作数(被减数)相加。
  2. 最高有效位的进位值反转(求反)后复制到进位标志中。

我们以1减2为例。假设操作数是8位的,在对2求反后,把两个整数相加,如下图所示:

如果把和(255)看做是有符号整数,那么结果实际上就是有符号数-1,运算结果是对的。下面是上面举例对应的汇编代码:

mov al,1
sub al,2        ;AL = FFh,CF = 1

INC和DEC指令不影响进位标志,对非零操作数执行NEG操作则总是设置进位标志。

辅助进位标志:辅助进位标志(AC)表示运算结果(目的操作数)的第3位向第4位的进位值。进位标志主要用于编码十进制数(BCD,Binary Coded Decimal)算术运算中(参见7.6节),不过也可用于其他情形。假设1和0Fh相加,和(10h)的第4位为1,这实际上是第3位的进位值:

mov al,0Fh
add al,1        ;AC = 1

下面是运算过程的图示:

   00001111
+ 00000001
   00010000

奇偶标志:奇偶标志在运算结果(目的操作数)的最低有效字节中为1的位数是偶数时置位。下面的加法和减法操作改变了奇偶位:

mov al,10001100b
add al,00000010b        ;AL = 10001110,PF = 1
sub al,10000000b        ;AL = 00001110,PF = 0

在ADD指令执行后,AL包含二进制数10001110(4个为0的数据位和4个为1的数据位),PF=1。在SUB指令执行后,AL中为1的数据位的个数是奇数,因此PF=0。

有符号运算:符号和溢出标志

符号标志:有符号算术运算的结果为负时,符号标志置位。下面的例子从一个较小的数(4)中减掉一个较大的数(5):

mov eax,4
sub eax,5        ;EAX = -1,SF = 1

机械地看,符号标志就是运算结果最高位(被舍弃)的副本,下面的例子中给出了BL的结果的十六进制值,其结果为负(-1):

mov bl,1        ;BL = 01h
sub bl,2        ;BL = FFh(-1)

溢出标志:有符号算术运算结果上溢(太大)或下溢(太小)以至于目的操作数无法容纳时,溢出标志置位。例如,从第1章中我们已经知道一个字节所能存储的最大有符号整数是+127,如果再给它加1的话将导致上溢:

mov al,+127
add al,1        ;OF = 1

类似地,一个字节所能容纳的最小负整数是-128,如果从中减1的话将导致下溢。目的操作数中存放的算术运算的结果是无效的,溢出标志被置位:

mov al,-128
sub al,1        ;OF = 1

加法测试:在两个有符号整数相加的时候,有一种非常简单的方法可以判断是否有溢出。发生以下情况说明发生了溢出:

  • 两个正数相加的和是负数
  • 两个负数相加的和是正数

但两个加数的符号不同的时候,永远不会发生溢出。

CPU是如何检测溢出的:在加法和减法运算完成后,CPU使用一种非常有趣的方法确定溢出标志的值:运算结果最高有效位向高位的进位值(CF的值)与到最高有效位的进位值异或,其结果放到溢出标志中。例如8位二进制数10000000和11111110相加,第6位向最高有效位(第7位)无进位,但是第7位向高位有进位值(CF= 1):

由于1 XOR 0 = 1,因此OF=1。

NEG指令:如果目的操作数无法正确存储,那么执行NEG指令可能会产生无效的结果。例如,如果将-128送AL寄存器并对其求反,结果+128无法在AL中存储,这会导致设置溢出标志,这时表明AL中的值无效:

mov al,-128        ;AL = 10000000b
neg al             ;AL = 10000000b,OF = 1

相反,如果对+127求反,结果是有效的,同时溢出标志被清零:

mov al,+127        ;AL = 01111111b
neg al             ;AL = 10000001b,OF = 0

CPU如何知道算术运算是有符号运算还是无符号运算的呢?我只能给出一个初听起来使人发懵的回答:CPU并不知道!CPU在算术运算之后根据一系列逻辑规则设置各种状态标志,它并不知道哪些标志对程序员是重要的,程序员自己应该根据执行的操作的类型来选择解释哪些标志和忽略哪些标志。