本文原创,转载请注明出处https://blog.csdn.net/C_ElecM/article/details/108911208
上过数电的都知道,计算机存放数据分两种情况,即有符号存储和无符号存储。无符号存储是直接存储其二进制值,而有符号存储则是存储其补码。以8位为例,如存储无符号数12,则在计算机中存储的值应为0CH;如要存储有符号数-12,则内存单元应该存放的值为F4H,这个存放的计算过程由编译器完成。也就是说,最终在计算机中存放的数就是一些二进制值,并没有有符号和无符号之分,当我们认为这些二进制值是有符号数时,那这些数就是有符号数,如果认为是无符号数则就是无符号数。例如,某计算机内存单元的二进制是F4H,这个数对应的无符号数为244,对应的有符号数时-12。没有谁规定到底是哪种数。所以该数是有符号还是无符号取决于观察者(编程者)。
在C语言中,我们熟悉的unsigned char a=244;printf("%d,%d",(unsigned char)a,(signed char)a);这个语句可以很好的说明这点。a的值没变,但是输出的结果却不相同,这取决于我们看物体的角度。那么计算机在存放244时存放的是啥呢?显然这取决于我们在定义a时的类型,如果是无符号的则存放二进制本身。而如果是有符号类型,则存放其补码,这里显然存放其二进制本身。
数的静态问题解决了,那么应该如何面对数的动态问题呢?下面我们来讨论数的动态情况。
在讨论前,我们先给出两条原则:
进位/借位只针对无符号数,进位的产生意味着无符号数的运算结果超出了范围,但并没有错误,只需考虑进位就可以得到正确结果。 溢出针对的是有符号数,溢出的产生意味着有符号数的运算错误,考虑溢出位也无法得到正确结果。计算机在进行加减时,会将两个数按照无符号数进行相加,在运算过程中可能会有进位出现,计算机会根据最高位和次高位的进位来判断是溢出还是进位,如果最高位和次高位均发生进位,则计算机就判定为进位,则把CF置位,如果仅最高位发生进位,则判定为溢出,则把OF置位。这里需要注意,这两个检测是硬件完成的,而且是同时给出CF和OF。例如:
计算-20+(-20),CPU会这样计算:
1. 先把两个数直接相加,11101100+11101100=111011000,OF=0, CF=1
2. 把11011000存回内存单元
计算-120+(-120),CPU会这样计算:
1. 先把两个数直接相加,10001000+10001000=100010000,OF=1, CF=0
2. 由于OF=1,产生溢出错误,此时CPU一般会发出异常。
上面举例说明了两个有符号数的计算过程。而无符号数也是一样的,只是无符号数没有补码之分,运算过程是相同的。
硬件上的实现是通过最高位进位和次高位进位的异或运算取得的,其硬件示意图如下:
计算机在进行运算时,这些标志位会在运算过程中同时产生,这些标志位是留给后续代码做参考的。
上面我们讨论了数的存取和运算,在其过程中可以发现,不论是有符号还是无符号,其计算过程是一样的,只是看待的角度不一样,所以在设计计算机指令时,没有必要设计出有符号加法和无符号加法两套指令,而只需要设计一套指令,再增加进位、溢出标志位、符号标志位等即可。这也就是为什么在很多计算机只有加法指令和减法指令,而没有有符号加法指令和无符号加法指令。从本质上说,无符号加法运算器再外挂一个进位和溢出标志电路即可成为无符号和有符号运算器。
可能有读者会问,既然补码可以实现减法,那计算机中为啥还是有减法指令,其实减法指令的出现是为了让第二个操作数产生一个“负号”,在硬件上表现为一个控制求补码的开关,产生补码后再给硬件做加法运算。所以本质上CPU中只有一个加法器,而无减法器。这样做的目的就是减轻了程序员的负担,没人喜欢把所有的减法操作都改为前一个数加上后一个数的相反数这种形式。
也有读者可能会问,这些标志位是用来干什么的,这确实有必要说明一下标志位的作用,在CPU中,标志位的最大的一个作用就是用作跳转语句的条件判断,在使用cmp这类比较测试指令时,CPU会做一次减法操作,然后根据标志位来判断两个数的大小,这样肯定有人会问,为啥不直接用一个比较器来进行判断,其实使用比较器确实可以,但是这样会增加不必要的成本,同时速度上也没有进位标志的方法快。另一个作用就是用来判断运算结果的准确性,如果在进行运算中,运算器发生了溢出,则说明上次计算结果出错,是否进行位扩展再重新计算还是直接舍弃。同时在进行两个较大的数运算时,由于8位不够用,可以进行拆分成2个8位或者多个8位,然后根据进位标志来进行高位运算,最后将结果拼接起来即可得到较大数值的运算。
其实,很多人很容易忽视这些看似简单的东西,而恰恰是这些东西构成了计算机运算的基础,同时理解这些问题也会为后面的模拟器的实现扫清道路。