C语言的整形溢出,分为有符号整形溢出和无符号整形溢出。signed and unsigned。对于unsigned
整形溢出,C的规范是有定义的:溢出后的数会以2^(8*sizeof(type))
作模运算,也就
是说,如果一个 unsigned char
(1字符,8bits)溢出了,会把溢出的值与 256
求模。例如:
unsigned char x = 0xff;
printf("%d\n", ++x);
上面的代码会输出:0 (因为0xff + 1是256,与2^8求模后就是0)。
对于 signed
整型的溢出,C的规范定义是 "undefined behavior"
,也就是说,编译器爱怎么处理溢出就怎么处理。比如:
signed char x =0x7f; //注:0xff就是-1了,因为最高位是1也就是负数了
printf("%d\n", ++x);
上面的代码会输出:-128,因为0x7f + 0x01得到0x80,也就是二进制的1000 0000,符号位为1,负数,后面为全0,就是负的最小数,即-128。
另外,千万别以为 signed
整型溢出就是负数,这个是不定的。比如:
signed char x = 0x7f;
signed char y = 0x05;
signed char r = x * y;
printf("%d\n", r);
上面的代码会输出:123
相信对于这些大家不会陌生了。
unsigned 与 signed 区别
-
计算机最小的存储单位是 "位" 也就是
bit
,用来存放一个二进制数,即 0 或 1 。 8个二进制位为一个字节Byte
。 -
对于 16-bit(16位) 的计算机,int 是以2个字节来储存的,而 32-bit 的计算机,则是以4个字节,即32个bit来储存的。
如果想要明白 singed 与 unsigned 的区别,除了这两个基本知识,还需要了解整数在计算机中的存储方式,以16-bit 计算机为例,定义 int a = 1; 那么a的存储方式用表格来表示:
0000 0000 0000 0001
首先需要提到的一点是,在 C语言 中十进制的整数都会转化为二进制存储在计算机。上面所声明的 int a = 1
,也就是 signed int a = 1
,C语言默认整形是一个 signed 类型。上面
表格中最左端的为最高位,最右端的为最低位。
signed类型的整数,只用了去除最高位,剩下的15位来进行编码的,而最高位只是用来做标记(sign),标记整数的正负,0表示正,1表示负。所以对于 signed 整数的存储范围是 (-2^15 to 2^15-1) ,也就是 -32768 到 +32767
的整数。
而对于 unsigned 的整数,其16位全部用来编码,存储范围便是 (0 to 2^16-1) ,即 0 到 65535
的非负整数。但是需要提到的一点是,不管整数的类型是signed 还是 unsigned,都用了16位来存储,也就是说16位全部用来存储数据。
上面所看到 a=1 的存储方式,就是将十进制的 a 在程序员计算器上转化为2字节的2进制,然后将这个结果放到上面的表格里。(原码存储) 可是对于 int a = -1
是怎样存储的?也就是说负数的存储方式是怎样的?
负数是以(补码存储),即是以原码的补码形式存储:
原码
0000 0000 0000 0001
反码
1111 1111 1111 1110
补码
1111 1111 1111 1111
原码、反码与补码
我们知道,在计算机内部存储的带符号数都是以补码形式存储,用补码形式进行运算的。什么是一个数的补码?为什么要用补码?这要从数的原码、反码开始讲。我们以整型数为例,且假定字长为8位。
1.原码
整数X的原码是指:其符号位为0表示正,为1表示负;其数值部分就是X的绝对值的二进制数。如:
[+100]原=01100100 [+0]原=00000000
[-100]原=11100100 [-0]原=10000000 注意:在原码中,零有两种表示形式。
原码表示法简单易懂,与真值(带符号数本身)转换方便,只要符号还原即可,但当两个正数相减或不同符号数相加时,必须比较两个数哪个绝对值大,才能决定谁减谁,才能确定结果是正还是负,所以原码不便于加减运算。
2.反码
X的反码是指:对于正数,反码与原码相同;对于负数,符号位不变,其数值位X的绝对值取反(1变0,0变1)。如
[+100]反=01100100 [+0]反=00000000
[-100]反=10011011 [-0]反=11111111
注意:在反码中,零也有两种表示形式。
反码运算也不方便,通常用来作为求补码的中间过渡。
3.补码
X的补码是指:对于正数,补码与原码相同;对于负数,符号位不变,其数值位X的绝对值取反后在最低位加1。如:
[+100]补=01100100 [+0]补=00000000
[-100]补=10011100 [-0]补=00000000
注意:在补码中,零有唯一的编码,[+0]补=[-0]补=00000000。
补码运算简单方便,符号位可以作为数据的一位参与运算,不必单独处理;二进制的减法可用其补码的加法来实现,简化了硬件电路。
补码意义
[例子1]用8位二进制数分别表示 +0 和 -0。
解:我们知道,对于有符号数,我们规定最高位为符号位,0表示正数,1表示负数。剩余位为数值位,用来表示数的大小。
所以 +0 就表示为 0000 0000
,而 -0 表示为 1000 0000
。
[例子2]计算9-6的结果。
解:我们知道:9-6=9+(-6)=3
0000 1001
+ 1000 0110
-----------
1000 1111
结果为-15,明显不对。而如果我们采用补码来进行计算呢?
我们知道,9的补码是0000 1001
,-6的补码是1111 1010
,重新进行运算
0000 1001
+ 1111 1010
-------------
1 0000 0011
最高位的1溢出,剩余8位二进制表示的是3的补码。结果为3,正确。
[例子3]分析程序运行结果。
int
main(int argc, const char *argv[]) {
int a=100,b=-1;
printf("a=%d,%x,%o,%u\n", a,a,a,a);
printf("b=%d,%x,%o,%u\n", b,b,b,b);
return 0;
}
运行结果:
a=100,64,144,100
b=-1,ffff,177777,65535
[例子1]中,为什么同样一个0有两种不同的表示方法呢?
[例子2]中,为什么第一种计算方法会错,而用补码计算结果才对呢?
[例子3]中,为什么-1以十六进制、八进制以及无符号整型输出的结果分别变成了 ffff , 177777 , 65535 ?
这是因为在计算机系统中,数值一律用补码来存储。
主要原因:
1、统一了零的编码;
2、将符号位和其它数值位统一处理;
3、将减法运算转变为加法运算;
4、两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。