中间算术表达式整数溢出疑问:C语言运算顺序不同结果却相同?
为什么
unsigned char运算中m + a - b和m - b + a结果相同? 首先把你的代码贴出来方便讨论:
int main(void) { unsigned char a = 5; unsigned char b = 20; unsigned char m = 0xFF; unsigned char s1 = m + a - b; unsigned char s2 = m - b + a; printf("s1 %d s2 %d", s1, s2); return 0; }
你疑惑的点是:按照左结合规则,s1的计算中m + a似乎会溢出,但实际运行后s1和s2结果一致。原因其实有两个核心关键点:
1. 整数提升让运算根本没在unsigned char层面发生溢出
在C语言中,所有宽度小于int的整数类型(包括unsigned char)在参与算术运算时,会被自动提升为int类型(只要int能容纳该类型的所有取值,这在几乎所有现代系统上都是成立的——毕竟int至少是16位,而unsigned char是8位)。
所以你的代码里:
m + a实际是把m(255)和a(5)都转换成int后计算,得到260,这完全在int的取值范围内,根本不会溢出。- 接着减
b(20,同样被提升为int),得到240,最后赋值给unsigned char类型的s1时,因为240小于256,直接保留为240。
而s2的计算是m - b + a:
m - b先转换成int计算,255-20=235,加5得到240,赋值给s2也是240。
这时候两个表达式本质上就是int层面的255+5-20和255-20+5,结果自然相同。
2. 即使真的发生无符号数溢出,结果依然会相同
假设我们处于一种极端情况:int的宽度和unsigned char一样(比如某些古老的8位系统),这时候运算会在unsigned char层面进行,而无符号数的溢出是C语言明确定义的——遵循模2^N规则(N是类型的位数,这里是8,也就是模256)。
我们来手动计算:
- 对于
s1:(m + a) - b→(255+5) mod 256 = 4,然后4 -20,因为无符号数不能为负,所以等价于(4 -20 +256) mod256 =240。 - 对于
s2:(m -b) +a→255-20=235,235+5=240,直接得到240。
这是因为在模运算的规则下,加法和减法满足交换律和结合律,(x + y) - z 和 (x - z) + y 在模2^N下的结果是完全等价的,所以不管运算顺序如何,最终结果都会一致。
总结一下:要么运算被提升到int层面完全没溢出,要么即使在无符号char层面运算,模运算的特性也会让两个表达式结果相同。
内容的提问来源于stack exchange,提问作者npn
相关产品推荐
相关产品推荐

