在处理器内进行的很多算术逻辑运算,都会影响到标志寄存器的某些位。比如我们已经学过的加法指令add、逻辑运算指令xor 等。在下面的讲述中,请自行参考图6-2。
当运算结果出来后,如果最低8 位中,有偶数个为1 的比特,则PF=1;否则PF=0。例如:
顺序执行以上两条指令后,因为结果是1000100100101101B,低8 位是00101110B,有偶数个1,所以PF=1。
再如:
以上,因为最后ah 的内容是0xa7(10100111B),包含奇数个1,故PF=0。
当处理器进行算术操作时,如果最高位有向前进位或借位的情况发生,则CF=1;否则CF=0。比如:
这里,寄存器AL 自己和自己做加法运算,并因为最高位是1 而产生进位。结果是,进位被丢弃,AL 中的最终结果为零。进位的产生,使得CF=1。同时,ZF=1,PF=1。
下面是因有借位而使得CF 为1 的例子:
CF 标志始终忠实地记录进位或者借位是否发生,但少数指令除外(如inc 和dec)。
对于无符号数运算来说,进位标志CF 通常意味着得到了错误的计算结果,因为目的操作数没能容纳那个进位。这里有一个例子:
执行以上两条指令后,进位标志CF 为1,这是肯定的了,因为最高位有进位。从无符号数的角度来看,是255+2,结果应当是257。但是你看,因为寄存器AH 只有8 位,所以进位丢失,得到的结果是1,这明显是错的。
但是,如果上面进行的是有符号数运算,那么,这实际上是在计算-1+2(十进制),AH 中的最终的结果是1,这是正确的。
很显然,同样的运算,从无符号数和有符号数的视角来看,是不同的。但是,在所有的情况下,处理器都不可能知道你的意图,不知道你进行的是有符号数运算,还是无符号数运算。为此,它提供了溢出标志OF,该标志的意思是,假定你进行的是有符号数运算,如果运算结果是正确的,那么OF=0,否则OF=1。比如上面的例子,因为从有符号数的角度来看,是-1 和2 相加,结果为1,未溢出,故OF=0。简单地说,OF 标志用于指示两个有符号数的运算结果是否错误。
再看一个例子:
首先,本次相加,用二进制数来说就是01110000+01110000=11100000,最高位没有进位,故CF=0。
其次,从无符号数的角度来看(十进制),即112+112=224,并未超出一个字节所能容纳的数值上限255,结果是正确的。
但是,从有符号数运算的角度来看(十进制),即112+112=-32,两正数相加,结果为负,明显是错的,在这种情况下,OF=1。错误的原因是,两个正数112 和112 相加,理论上的计算结果224 超出了寄存器AH 所能容纳的有符号数的范围-128~127,所以破坏了符号位,使得结果变成了负数(-32)
既然如此,可以使用16 位寄存器AX,毕竟它能容纳的数据范围更大一些:
这次,无论它是有符号数运算,还是无符号数运算,结果都是正确的。故CF=0,OF=0。
因为在任何时候,处理器都不可能知道你的意图,不知道你进行的是有符号数运算,还是无符号数运算。因此,它所能做的,就是假定进行的是有符号数运算,并根据结果提供OF 标志,至于如何处理,是你自己的事。比如说,如果你进行的是无符号数运算,那么,你可以不用理会该标志。
由于是刚刚接触标志位,现将前面学过的指令对标志位的影响一一列举如下。在往后的学习中,但凡遇到新的指令时,除了讲解指令的功能和用法,也会说明其对标志位的影响。
注意,可以在Bochs 中察看标志位的状态,具体方法请参见本章后面的6.12.3 节。
“jcc”不是一条指令,而是一个指令族(簇),功能是根据某些条件进行转移,比如前面讲过的jns,意思是SF≠1(那就是SF=0 了)则转移。方便起见,处理器一般提供相反的指令,如js,意思是SF=1 则转移。爱上网的朋友们容易把它理解成“奸商”。
在汇编语言源代码里,条件转移指令的操作数是标号。编译成机器码后,操作数是一个立即数,是相对于目标指令的偏移量。在16 位处理器上,偏移量可以是8 位(短转移)或者16 位(相对近转移)。
相似地,jz 的意思是ZF 标志为1 则转移;jnz 的意思是ZF 标志不为1(为0)则转移。
jo 的意思是OF 标志为1 则转移,jno 的意思是OF 标志不为1(为0)则转移。
jc 的意思是CF 标志为1 则转移,jnc 的意思是CF 标志不为1(为0)则转移。
jp 的意思是PF 标志为1 则转移,jnp 的意思是PF 标志不为1(为0)则转移。爱上网的朋友们注意了,jp 可不是“极品”的意思。
转移指令必须出现在影响标志的指令之后,比如:
经验证明,像这种水到渠成的情况是很少的,多数时候,你会遇到一些和标志位关系不太明显的问题,比如,当AX 寄存器里的内容为0x30 的时候转移,或者当AX 寄存器里的内容小于0xf0的时候转移,再或者,当AX 寄存器里的内容大于寄存器BX 里的内容时转移,这该怎么办呢?
好在处理器提供了比较指令cmp,它需要两个操作数,目的操作数可以是8 位或者16 位通用寄存器,也可以是8 位或者16 位内存单元;源操作数可以是与目的操作数宽度一致的通用寄存器、内存单元或者立即数,但两个操作数同时为内存单元的情况除外。比如:
cmp 指令在功能上和sub 指令相同,唯一不同之处在于,cmp 指令仅仅根据计算的结果设置相应的标志位,而不保留计算结果,因此也就不会改变两个操作数的原有内容。cmp 指令将会影响到CF、OF、SF、ZF、AF 和PF 标志位。
比较是拿目的操作数和源操作数比,重点关心的是目的操作数。拿指令cmp ax,bx 来说,我们关心的是AX 中的内容是否等于BX 中的内容,AX 中的内容是否大于BX 中的内容,AX 中的内容是否小于BX 中的内容,等等,AX 是被测量的对象,BX 是测量的基准。比较的结果如表6-1 所示。
表6-1 各种比较结果和相应的条件转移指令
续表
非常显而易见的是,如果你英语基础比较好,认识上面那些单词的话,这些指令都可以在短时间内轻松记住。英语基础不太好的人也不要灰心,事实上,根本不需要记住这些指令和它们的测试条件,因为我们平时很少用得了这么多。需要的时候再回过头来查查,这是个好办法,时间一长,自然就记住了。
最后一个要讲述的条件转移指令是jcxz(jump if CX is zero),意思是当CX 寄存器的内容为零时则转移。执行这条指令时,处理器先测试寄存器CX 是否为零。例如:
这里,“show”是程序中的一个标号。执行这条指令时,如果CX 寄存器的内容为零,则转移;否则不转移,继续往下执行。
检测点6.4
1.ZF 标志位和与该标志位有关的条件转移指令用得非常频繁,但很多人容易在ZF 标志位上犯糊涂,以为计算结果为零时,ZF 为“0”。为了证明你不糊涂,请填空:当ZF=( )时,表明计算结果为零;jz 指令的意思是当ZF=( ),即计算结果为( )时转移;je 指令的意思是当ZF=( ),即计算结果为( )时转移;jnz 指令的意思是当ZF=( ),即计算结果不为( )时转移;jne 指令的意思是当ZF=( ),即计算结果不为( )时转移。
2.写一小段程序,先比较寄存器AX 和BX 中的数值,然后,当AX 的内容大于BX 的内容时,转移到标号lbb 处执行;AX 的内容等于AX 的内容时,转移到标lbz 处执行;AX 的内容小于BX 的内容时,转移到标号lbl 处执行。