C 语言整形溢出结果讨论

我是在编译器里用sizeof(int) 输出 4
说明我这是4个字节32位的编译器。

int a = 0x7fffffff; //有符号的 范围是 -2^31~2^31 = 2147483638 = 0x7fffffff
unsigned int b = ; //无符号的 有符号的话就是 0~2^32  = 4294967296 = 0xffffffff
 

那么根据c语言的定义

unsigned int溢出后:数会以2^(8*sizeof(type))作模运算

signed int 溢出后:undefined behavior

所以下面的代码输出为什么是?而且我把变量定义为sign 和 unsigned对输出的结果都没有影响,为什么?

printf("a: %d\n", a);  // 输出 a: 2147483647
printf("a: %d\n", a+1);  // 输出 a: -2147483648

谢谢大神们的指教!

阅读 3.8k
3 个回答

为了叙述方便,假设只有 3bit 表示一个整数。
一个 signed 的整型,表示范围是 -4 ~ 3,unsigned0 ~ 7。对于signed型,哪个二进制表示-4 -3 -2 ... 等有多种映射的方式。所以人们需要找一种比较好的方式,最终使用的方式就是现在的补码。
表示的方式就是:

二进制  unsigned  signed
000  0  0
001  1  1
010  2  2
011  3  3
100  4 -4
101  5 -3
110  6 -2
111  7 -1

这里多说一句对于signed负数a,其对应的二进制的转换成unsigned的b满足关系:a = b - 8

这个做的好处就是 unsigned signed 的加法(包括减法)是一样的运算方式。。。比如1+4=51+(-4) = -3 他们的表示二进制都是001+100=101
所以无论是unsigned还是signed他们其实计算机里都是相同的运算规则(加减法),只是"输出"的时候100被作为unsigned被解释为4,signed被解释为-4,而变量的类型说明了着一串二进制如何解释

对于函数printf,你有没有发现,其实他的声明只规定了第一个参数的类型,并没有规定后面参数的类型。这种函数其实是C语言里可变参数的函数类型。printf 是根据第一个参数中的%d之类的占位参数确定后面有几个参数以及占用了多少个字节。printf("%d %f\n",a,b) 的时候,通过%d printf 就把第二参数“认为”是signed占4个字节,然后再就知道了第三个参数的起始地址:

所以不论 a 是 unsigned 还是 signed a = 3(011) 加 1之后都是100,然后由于%d的原因printf 把其解释为signed得到-4.

首先,int无论在32bit抑或是64bit的机器上都为4个字节,所以不能根据sizeof(int)来判断编译器位数。
其次,int的范围是-2^31 ~ 2^31 - 1-2,147,483,648 ~ 2,147,483,647,所以最后代码输出是为两端的极值。
最后,你想将变量定义修改为unsigned时记得将打印函数设置为%u,否则编译器还是按照%d对应的signed int进行输出。

关于输出结果,@zonxin的答案说得很清楚了。

至于为什么有符号数的溢出是未定义行为,是因为有符号数具有多种表示,而C语言没规定表示形式(虽然一般使用补码),所以溢出后的二进制位代表什么是不可预料的(没有规定如何解释它)。

C99规定了无符号数的表示必须为其二进制形式,因此无符号数的溢出后行为是可预期的。

Values stored in unsigned bit-fields and objects of type unsigned char
shall be represented using a pure binary notation.

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏