c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)

初学者在学习C语言,谈到不同数据类型时,一般都能理解 unsigned 和 signed 的区别,无非就是有无符号而已。但是对于 signed 数据类型的数据范围,初学者却常常会感到迷惑。

c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)(1)

对于 signed 数据类型的数据范围,初学者却常常会感到迷惑

问题

例如提到 char 型能够表示的数据范围时,unsigned char 类型很好理解,所有的 8 位全部用于表示数值,因此 unsigned char 类型能够表示 2^8 = 256 个数,例如 0 到 255。

对于 signed char 类型,最高一位表示符号( /-),能够用于表示数值的是低 7 位。因此按理说,signed char 类型能够表示的数值范围为 -0b111111到 0b1111111,也即 -127 到 127。

但是很多教科书上却说 signed char 类型能够表示的数值范围为 -128 到 127,类似的还有 signed short 类型能够表示的数值范围为 -32768 到 32767,signed int 类型能够表示的数值范围为 -2147483648 到 2147483647,为什么有符号的整数类型,负数部分总是比整数部分多出一个数呢

这里假设 short 类型占用 2 字节内存空间,int 类型占用 4 字节内存空间。

c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)(2)

为什么有符号的整数类型,负数部分总是比整数部分多出一个数呢?

初步分析

在C语言中,char 型变量占用 8 个位,对于 signed char 类型,最高位表示符号位,此时有 7 个位用于表示数值。按照数学中的排列组合,7 个位能够表示 2^7 也即 128 个不同的数,若考虑正负号,signed char 类型最多也能表示 2*128=256 个不同的数。

但是,如果 signed char 类型能够表示的数值范围是 -0b111111到 0b1111111(-127 到 127),那么能够表示的只有 255 个不同的数字了,与理论最大能够表示的不同数字数 256 相比,少了一个,这是因为 -0 和 0 其实是同一个数字,也即 0b10000000 和 0b00000000 是同一个数字 0。

这对于计算机来说很不友好,同样的一个数字有两种二进制码,在处理时会显得很麻烦。这种麻烦对于 CPU 的电路设计,同样如此。

谈到 CPU 的电路设计,我们还应该明白,固定的面积上能够容纳的电路单元数是固定的。如果 CPU 只需处理加法运算,那么设计师就只需设计加法电路,这样既简单,又能最大程度的保障计算力。

c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)(3)

简单就好

可是在实际应用中,不可避免的需要减法运算,再考虑到 /- 0 导致的数字二进制码重复问题,计算机中“补码”的概念被提出了。

补码

关于“补码”的定义就不写了,感兴趣的读者可以自行百科。这里仅简要的说下C语言中补码是如何获得的,其实很简单:正数和 0 的补码等于自身,负数的补码则是将其对应正数按位取反再加1。

例如正数 0b00000011(3) 的补码等于其自身,仍然为 0b00000011,而 0b10000001(-1)的补码等于 0b11111110 1 也即 0b11111111(0xFF)。

补码的最大优点是可以在加法或减法处理中,不需因为数字的正负而使用不同的计算方式。只要一种加法电路就可以处理各种有符号数加法(减法可以用一个数加上另一个数的补码来表示),因此只要有加法电路及补码电路即可完成各种有符号数加法及减法,在电路设计上相当方便。

c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)(4)

判断数字是否为 0 时,只要比较一次即可

另外,补码下的 0 就只有一个表示方式,因此在判断数字是否为 0 时,只要比较一次即可。

简单来说,数字 a(正负数皆可)的补码即为 -a。为什么 signed char 型能够表示的数值范围为 -128~127

因为 -0 和 0 其实是同一个数字,因此原码中 0b10000000 和 0b00000000 都表示数字 0。现在补码下的 0 只有一个表示方式:0b00000000,二进制码 0b10000000 就多余出来了。

浪费是可耻的,多出的二进制码 0b10000000 不能白白丢弃。若考虑数字 0 的二进制码 0b00000000,从它的符号位来看,计算机应该是将其当做“正数”的,0~127 是 128 个“正数”。

现在考察多出的二进制码 0b10000000,从它的符号位来看,把它当做负数是合情合理的,事实上在C语言中,它表示-128,从 -128 到 -1,恰好是 128 个“负数”。

在C语言中,signed char 型二进制码 0b10000000 的补码仍然为 0b10000000,因此它是“数字a的补码为 -a”原则的例外。

下表是一些 char 类型整数的补码,可表示的范围为 -128 到 127,总共 256 个不同整数。

c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)(5)

char 类型整数的补码,可表示的范围为 -128 到 127

延伸:为什么补码能这么巧妙实现了正负数的加减运算?

答案是:指定 n 位字长,就有 2^n 个可能的值,加减法运算都存在上溢出与下溢出的情况,实际上都等价于模 2^n 的加减法运算。这对于 n 位无符号整数类型或是 n 位有符号整数类型都同样适用。

例如,8 位无符号整数的值的范围是 0 到 255。因此 4 254 将上溢出,结果是 2,即

4 254 = 258 = 2 (mod 256)

8 位有符号整数的值的范围,如果规定为−128到127,则 126 125 将上溢出,结果是−5,即

126 125 = 251 = -5(mod 256)

c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)(6)

为什么补码能这么巧妙实现了正负数的加减运算?

对于 8 位字长的有符号整数类型,以 2^8 即 256 为模,则

-128 = 128 (mod 256) -127 = 129 (mod 256) ... -2 = 254 (mod 256) -1 = 255 (mod 256)

所以模 256 下的加减法,用 0, 1, 2,…, 254,255 表示其值,或者用 −128, −127,…, −1, 0, 1, 2,…,127 是完全等价的。−128与128,−127与129,…,−2与254,−1与255 可以互换而加减法的结果不变,需要的 CPU 加法运算器的电路实现与 8 位无符号整数并无不同。

实际上对于 8 位的存储单元,把它的取值 [00000000,…, 11111111] 解释为 [0, 255],或者 [-1, 254],或者[-2, 253],或者[-128, 127],或者[-200, 55],甚至[500, 755],对于加法硬件实现并无不同,都是一样的。

小结

本节主要讨论了C语言中 signed char 型变量能够表示的数值范围,一般认为其能够表示 -128~127 的整数,而不是 0b11111111(-127)到 0b01111111(127),这也是初学者常常感到迷惑的地方。其实简单来看,计算机的设计讨厌一切“浪费”,数字 0 用两个二进制码( /-0)表示就是一种浪费,处理起来也比较麻烦。倒不如只为 0 保留一种二进制码 0b00000000,而将多出的二进制码 0b10000000 用于表示 -128 方便了。

c语言unsigned与int区别(C语言中signedchar类型为什么表示-128到127)(7)

点个赞再走吧

欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

未经许可,禁止转载。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页