复习背景
事情是这样的:cmp指令配合SF和OF两个标志寄存器进行逻辑上的判断。可这个判断下面,会涉及二进制数据间的加减,而加减又要区分有符号、无符号情况,以及存储的规律。于是,我在补码知识上的欠缺让整个学习过程摇摇欲坠。
分析"三码"
原码众所周知,不再赘述。
反码
反码的意义:
在计算机的数字电路中只有加法器,没有所谓的“减法器”。不是说计算机厂商不会设计减法器,因为聪明的人既然发明了方法能够用加法来实现减法操作,那为什么还需要画蛇添足的弄一个减法器?
示例:3+[-5]=[-2]的计算过程为:
[0_0000011]+[1_1111010]=[1_11111101]
补码
补码的出现是因为“0”。
00000000和11111111分别表示 +0和-0,可0作为一个数据,不能有两种表现方式而引起矛盾。所以+0被保留了下来,而-0的归处则是同其他负数 一起增加1位,从-0~ -127 变为 -1 ~ -128 。
关于这一点:
- 这是为什么同一内存空间的数据范围,负数绝对值的最值要比正数大1。
- 这也是补码是反码+1的原因。
- 这也解释了:-128为什么没有原码、反码。
至此,通过补码就成功解决了数字0在计算机中非唯一编码的问题,且也能实现减法变加法。
一个比方:
反码就是讨巧了数位循环,好比时钟一圈12小时,往后拨5小时与往前拨7小时是等效的;而现在换成1个字节,一整圈以255为循环(+0和-0重叠),-5和+250等效。补码的意义就是把0的重叠问题解决了,一整圈以256为循环,-5和+251等效。
所以,在计算机的世界里,0是正数。这点和我们学的数学不一样。
{0_1111111}=+127 (补码)
{0_0000000}=+0 (补码)
{1_1111111}=-1 (补码)
{1_0000000}=-128 (补码)
扩展:
-
减法变加法只是CPU的一个运算过程,它并不会去修改参与运算数存储的值,你可以理解为CPU从BX里面把5取出来之后,有专门的电路负责把它变成-5的补码,然后再参与加法运算。BX的值是我们人为赋予的5,当然不能为了做这样一个减法而随意去修改它。
-
计算机真正的编码过程,不需要考虑和判断所谓的第一位正负号“符号位”,只要看到符号“-”就重复“反码+1”的取相反数过程。
-
配合 2 来说,补码就是一种取相反数的机制,这种相反数的机制使计算机在底层算术运算时不需要人为判断符号位。但在抽象层高了以后的操作中,像输出到屏幕API、printf语句等,会由编译器来判断符号位。
例如,对一个编码0xF0,当它存储在内存中的时候,如果是无符号数,应等于240;如果是有符号数,应等于-16。那它究竟是代表哪个数呢?计算机在硬件层面并不需要知道它代表的是240还是-16,因为它是正是负都不影响和别的任何数字做加减操作。比如在与数字2的编码值0x02相加之后,编码值是0xF2,那这个编码值就可以代表是242或-14。
-
乘除法的过程,符号位会参与运算。因为符号位参与了计算过程,因此你在做乘除法之前,就必须在硬件层面指定0xF0代表的是正数240还是负数-16。正因为如此,汇编程序在做乘除法的时候,必须要指定是无符号乘除还是有符号乘除,无符号除法用指令:DIV,有符号除法用指令:IDIV。
-
循环进位:反码的符号位相加后,如果有进位出现,则要把它送回到最低位去相加,如:3 + (-2) 和 3 +(-1)
PS:这里存在大量摘录,(同时更详细的实现过程可以看)源网页:
—>参考:为什么用补码
当然,补码作为计算机能够处理的最终数据形态,将是真正存放在内存和硬盘里的数据。不信我们看下补码定义:
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。
引出问题
困扰我的问题是:
对于同一个存储的数据,它何时作为正数补码,何时作为负数补码?
这里我给出的解释是,底层数据中,正数补码和负数补码当然是一模一样的,也就是说计算机会把这两种情况都考虑在内,然后凭借标志寄存器(程序状态字)同时记录两种情况需要的标志。
问题背景下,CF与OF的深入
如:无符号情况的CF的借位,有符号情况的OF的溢出。
(同一问题的两个方面)
对于CF来说:
- 进位:无符号数相加时,最高有效位向更高位进位,表示无符号数运算发生上溢出。
- 借位:无符号数相减时,最高有效位向更高位借值,表示无符号数运算发生下溢出。
对于OF来说:
溢出是针对有符号数而言的,即运算结果超出最大范围。只有当两个相同符号数相加,而运算结果符号与原数据符号相反时(MSB为符号位),产生溢出。
(概括:溢出只会出现在两正数或负数相加的情况)
不过需要注意的是,两个负数的补码,原最高位相加后的进位由硬件自动舍出,不考虑在数据和溢出范围之内。
解决:回到cmp
cmp的操作会发生溢出的情况,这种情况下,sf的值可能会产生“误导”,即cmp的比较结果只能记录实际结果的正负,不能得到真正的逻辑结果。它还需要借助OF。
;关于逻辑结果和实际结果:
mov ah,08ah
mov bh,070h
cmp ah,bh
;实际结果得到1AH, 值为26 ,SF=0;
;逻辑结果应为(-118)-112=-230; 即包括溢出位的11AH
;溢出会让逻辑结果与实际结果相反。
cmp要配合SF,OF。
cmp A B :无外乎四种情况:
-
SF = 1 ,OF = 0
OF=0没有溢出,实际结果=逻辑结果。
SF=1,实际结果为负数。A-B<0。
A<B -
SF = 0 ,OF = 0
OF=0没有溢出,实际结果=逻辑结果。
SF=0, 实际结果为非负(正数或0)。A-B≥0。
A≥B -
SF = 1 ,OF = 1
OF=1存在溢出,实际结果与逻辑结果相反。
SF=1,实际结果为负,逻辑结果为正。A-B>0
A>B -
SF = 0 ,OF = 1
OF=1存在溢出,实际结果与逻辑结果相反。
SF=0,实际结果为正,逻辑结果为非负。A-B≤0
A≤B
溢出导致实际结果与逻辑结果相反的解释:这个结果是配合cmp造成的。注意cmp A B ,从运算角度看,是A-B。这个减号意味着只有A和B异号的情况才会导致溢出(即两负数或两正数加和的情况)。
在AB异号的条件下:
-
正数加和(正减负)溢出的实际结果一定为负数。以8位内存数据为例,0~127的两个数据,溢出的情况无外乎127+1到127+127。而这个范围内的和 不会摆脱 -1 ~ -128 的补码形式。(说到这里,你想起上文提到的“循环”"钟表"例子了吗)
-
负数加和(负减正)与之同理。
附赠(debug模式查看标志寄存器)
标志 | 中文 | Y | N |
---|---|---|---|
OF | 溢出(是/否) | OV OVerflow | NV Not oVerflow |
DF | 方向(减量/增量) | DN DowN | UP UP |
IF | 中断(允许/关闭) | EI Enable Interrupt | DI Disable Interrupt |
SF | 符号(负/正) | NG NeGative | PL PLus |
ZF | 零(是/否) | ZR ZeRo | NZ Not Zero |
AF | 辅助进位(是/否) | AC Auxiliary Carry | NA Not Auxiliary |
PF | 奇偶(是/否) | PE Parity Even | PO Parity Odd |
CF | 进位(是/否) | CY CarrY | NC Not Carry |