计算机补码:负数赋值给unsigned char的“奇怪”结果

在《C++ Primer》(第五版)中,第2.1.2小节类型转换内容中,有一行示例代码:

unsigned char c = -1   // 假设char占8比特,c的值为255

看起来结果有点奇怪,回想当年大学时代对计算机底层编码的遥远回忆,才发现这涉及到计算机底层存储原理。先说结论:

1)计算机基础原理是二级制,底层bit(比特)只认0和1,8比特为1个byte(字节),通常2个或4个字节为一个word(字)

2)数字有原码、反码、补码三种形式,一般用第1个bit位来区分正负数,0位正数,1表示负数。约定正数的反码、补码是与其原码相等的;针对负数,反码指除首个符号位之外,其余位取反(即0->1, 1->0),补码则是符号位不变,在反码基础上再加1

3)目前计算机底层都是以补码的形式来存储,其好处是将减法也变成了加法操作,从而简化底层电路操作。而且以补码形式相加,首个符号位也参与计算,加法时不用计较正负

4)补码形式存储,还有一个好处是,1000...0(即原码形式的“-0”)被用来表示最小值。所以1字节有符号整数的范围是[-2^7, 2^7 - 1]=[-128, 127]。

上述结论的得出,可以进一步参照原码, 反码, 补码 详解

现在看下-1-127=(-1) + (-127)在计算机底层具体是怎么操作的(假定unsigned char占1个字节,8比特):

-128=-1-127=(-1) + (-127) = [11111111]补 + [10000001]补 = [10000000]补  (注:补码相加时首个符号位也参与相加,若首个字符位相加有进位,则直接丢弃)

其中-1 = [10000001]原 =  [11111110]反 = [11111111]补,-127 = [11111111]原 =  [10000000]反 = [10000001]补 (注:公式中原、反、补分别指原码、反码、补码)

所以,从上述示例中,可以看出在补码存储方式下,减法变成了加法。同时 “[10000000]补”比较特殊,被强制性地解读为-128。其他负数的补码按常规方式(见文章开头第2条结论)来解读。

最后,再回到将-1初始给一个unsigned char变量后变成255,就很好理解了。因为-1的补码是11111111,底层直接赋给unsigned char变量,它是个正数,而正数的原码、反码、补码是一模一样的,所以11111111按原码来解读出来,就是255!(当然,还有一种快速解答方式,将一个负数赋给一个无符号整数,其实际值是该负数+模数,本例是-1+256,以此类推)

短短的一行代码,如果细究,将重新刷新你对计算机底层原理的理解。

如果这是一道面试题,如果没有事先了解,肯定无法作答。看完这篇文章,你还可以反问面试官,计算机底层为什么只需一个加法器,而不用再设计一个减法器?

你可能感兴趣的:(C/C++)