文章目录
0 摘要
本文以表格的形式讨论了C语言整型数据类型的特点,以及原码、反码、补码和整型取值范围的关系;基于对整型取值范围的认识,对整型溢出的原因做了一个大致分类,也从汇编的层面去探讨了整型“下溢”的本质。
1 整型数据类型
整型数据类型是C语言中的一种基本类型,其存储的是整数,可分为两大类:有符号整型和无符号整型。
1.1 位宽、符号和取值范围
各整型类型的位宽及取值范围如下表所示。
类型 | 位宽 | 最小值 | 最大值 |
---|---|---|---|
char | 8 | -128 | 127 |
unsigned char | 8 | 0 | 255 |
short | 16 | -32 768 | 32 767 |
unsigned short | 16 | 0 | 65 535 |
int | 32 | -2 147 483 648 | 2 147 483 647 |
unsigned int | 32 | 0 | 4 294 967 295 |
long | 32 | -2 147 483 648 | 2 147 483 647 |
unsigned long | 32 | 0 | 4 294 967 295 |
long long | 64 | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
unsigned long long | 64 | 0 | 18 446 744 073 709 551 615 |
-
位宽
不同的整型类型,有不同的位宽。需要注意的是,不同环境下某些整型类型的位宽定义可能不一样,比如64位Ubuntu17.10+GCC7.2.0编译器的long和64位win7+VS2010的long位宽不一样。16位系统的int和32位系统的int位宽不一样。具体位宽可以通过sizeof()来获知。 -
符号
无符号整型和有符号整型在编码上的区别在于“符号位”的有无。下图以8位的整型为例,有符号整型的最高位用作符号位,其余位用来表示数值的绝对值,符号位为1代表这是一个负数,为0表示正数。而无符号数没有符号位,整个内存区间都用来表示数值大小。
-
取值范围
符号位的有无和位宽的大小决定了整型数据的取值范围。对于位宽为n的无符号整型,其取值范围为:[0, 2n-1],这是显而易见的。对于位宽为n的有符号整型,其取值范围为:[-2n-1, 2n-1-1],为什么是这样的取值范围,后面讨论。
1.2 原码、反码和补码
下面的表格是正数、负数以及一些具体数值的原码、反码和补码的关系示意。表格第三列中的符号“~”和第四列中的符号“>>”分别代表“按位取反”和“右移”,跟C语言里的定义一样。
十进制 | 原码 | 反码 | 补码 | 右移操作 |
---|---|---|---|---|
正数 | 符号位 = 0 | = 原码 | = 反码 = 原码 = ~负数补码 + 1 |
最高位补0 |
负数 | 符号位 = 1 | 符号位不变, 其余各位取反 |
= 反码 + 1 = ~正数补码 + 1 |
最高位补1 |
4 | 0000 0100 | 0000 0100 | 0000 0100 | 4 >> 1 = 0000 0010补码 = 2 |
-4 | 1000 0100 | 1111 1011 | 1111 1100 | -4 >> 1 = 1111 1110补码 = -2 |
1 | 0000 0001 | 0000 0001 | 0000 0001 | 1 >> 1 = 0000 0000补码 = 0 |
-1 | 1000 0001 | 1111 1110 | 1111 1111 | -1 >> 1 = 1111 1111补码 = -1 |
-
原码
原码就是“最高位表示符号,其余位表示数值的绝对值”的编码。 -
反码
相对原码而言,正数的编码不变,负数的编码除符号位外所有位都按位取反,就是反码。 -
补码
相对反码而言,正数的编码不变,负数的编码加1,就是补码。这是计算机存储数值的编码方式。
1.3 再论有符号数取值范围
前面说到,位宽为n的有符号整型,其取值范围为:[-2n-1, 2n-1-1]。以char型为例,则其取值范围为[-128, 127]。为什么取值范围是[-128, 127],而不是[-127, 128]呢?
我们知道原码非常直观而容易理解,但是这种编码在计算机上进行运算是比较复杂的——需要先判断正负,再进行加减。相比之下,反码的运算则比较简单,因为反码让符号位也参与了运算(省去了符号位的判断),让减法可以用加法来表示(省去了专门的减法电路),比如4-4,可以表示为4+(-4),即0000 0100反码+1111 1011反码=1111 1111反码=1000 0000原码=0
但反码有个问题:由于0的原码有两个:“正零”00000000原码和“负零”10000000原码,导致反码也有两个0:“正零”00000000反码和“负零”11111111反码。
如下表所示,原码和反码的“零”都有两种编码。
十进制 | 原码 | 反码 |
---|---|---|
-127 | 1111 1111 | 1000 0000 |
-126 | 1111 1110 | 1000 0001 |
… | … | … |
-2 | 1000 0010 | 1111 1101 |