1.先从一个列子引出问题:
//sample_1 unsigned char
#include <stdio.h> #include <stdlib.h> int main() { unsigned char a = 255; a = a + 1; printf("a = %d\n", a); return 0; }
输出结果:
为什么是0 而不是256, 这是很显然的发生了溢出。现在从机器存储的角度来看这个问题:
在C语言中,char表示占用一个byte空间,在机器上一个byte可存储的范围是0000 0000 b ~ 1111 1111 b, 现在不考虑符号问题,仅仅把它解释为无符号的整数,那么对应十进制范围是0 ~ 255, 现在我们的a初始值是255 即 1111 1111 b,接着给它加1,那么二进制运算后变为 1 0000 0000 b, 由于进位,结果变成了9个bit,但是别忘了char是只占一个byte的,所以最前面的那个bit 将会被丢弃,结果变成 0000 0000 b, 因此输出结果会是0.
这个列子说明,整数在机器上存储的方法是满足二进制的运算律的,但是当用一定类型表示出来时,这个二进制的值就要按照那个类型的约束表现出来,就像上面的列子一样,即使最后的二进制结果是9个bit,但是char类型明确约束你只能占8个bit,最后只能丢弃1个bit之后按8个bit的结果展示出来。
2.第二个列子,引入有符号类型的表示问题:
//sample_2 signed char
#include <stdio.h> #include <stdlib.h> int main() { char a = 127; a = a + 1; printf("a = %d\n", a); return 0; }
输出结果:
先不忙分析问题原因,先来搞清楚负数是如何在计算机上存储的。简单的说,计算机擅长做0和1的加法,并不愿意像人类用的十进制一样去引入负号这个东西,否则会增加硬件和软件的复杂性和成本,如何解决这个问题,众所周知的就是用补码。可以举个列子去理解补码的作用,1+(?)= 0,很显然 1 + (-1)= 0, 这样就把减法问题转化为加法问题,那么在计算机中怎么去表示(-1)? 同样的,我们站在计算机的角度想问题(先拿一个byte的来举例),(0000 0001 b) + (?) = (0000 0000 b), 当然是(0000 0001 b) + (1111 1111 b) = (0000 0000 b),因此(1111 1111 b) 就是十进制1(原码)的补码,教科书中说补码可以通过对原码取反加一得到,其实通过上面的列子可以换一种角度解读补码的含义:即补码就是那个使得和原码相加之后结果能够为0的数。说了这么多,就是想说明对一个byte来说,(-1)在计算机上存储为(1111 1111 b)。
那么对于有符号char型来说,还是一个byte的空间,还是0000 0000 b ~ 1111 1111 b的范围,但是现在要把这个范围按有符号整数的表示来翻译了,可以分为3部分来理解,即 < 0, 0 , > 0,0000 0000 b显然是0, 0000 0001 b ~ 0111 1111 b显然是 1 ~ 127, 1000 0000 b ~ 1111 1111 b 是 –1 ~ –128。由此也可以发现,可以把最高位作为符号位来理解,0正1负。
换一种表达方式 (图画的太渣了,求推荐好的画图工具)
上面这两张图希望能表达清楚我上面的分析结论,即计算机上存储的整数一定是满足二进制的运算律的,但是怎么去解释它就取决于整型变量的类型约束条件了。
说了这么多,再来解释第二个列子就简单多了。第一步char a = 127 说明a是有符号的,127在计算机上存储为0111 1111b, 第二步 a = a + 1,a在计算机上变成了1000 0000b, 对应上面的分析,把它按照有符号数来解释即 -128,因此输出-128。
3.上第三个列子,关于符号位扩展问题:
//sample_3
#include <stdio.h> #include <stdlib.h> int main() { char a = 127; a = a + 1; printf("a = %u\n", a); return 0; }
输出结果:
我只是将sample_2的代码中%d改成了%u,输出结果就差之千里,其实还是一个道理,用不同的方式解释计算机存储的二进制值就会得到不同的结果。当然这里还涉及符号位扩展以及类型提升。
首先说类型提升,%d表示打印int类型有符号十进制数,%u表示打印int无符号十进制数,当a被打印时隐式的发生类型提升,即char被提升为int,即8位被扩展为32位。
对于sample_3来说:
a的值在1个byte中,是 0111 1111b
a加1后在1个byte中,是 1000 0000b
a的值被类型提升并扩展为4个byte后,是11111111 11111111 11111111 10000000 b,按照%u来解释是多少呢,当然是4294967168。
可是为什么扩展后前面的空位都是补1而不是补0呢,这个有个原则是 “有符号数扩展符号位,也就是向前补1,无符号数扩展0”。
那么sample_2应该也发生类型提升和符号扩展,为什么没出问题呢。因为虽然a的值被类型提升并扩展为4个byte后,依然是11111111 11111111 11111111 10000000 b,按照%d来解释,他是00000000 00000000 00000000 10000000 b的补码,所以表达为-128。
以上是自己的一些理解,难免有错误,欢迎讨论。