704 字
4 分钟
[编程] C 语言陷阱:整数平方溢出
在处理数值运算时,如果不关注类型的取值范围,即使是简单的平方运算也会产生令人困惑的结果。
1. 现象描述
观察以下代码片段:
int x = 65535;int y = x * x;printf("%d\n", y);运行结果在大多数 32 位系统下,输出结果不是
4294836225,而是-131071。
为什么一个正数的平方会变成负数?
2. 深度原因分析
问题的核心在于 32 位有符号整数(int)的溢出 以及 计算机底层的补码表示法。
2.1 数值大小对比
- 理论结果:。
- int 类型的上限:在 32 位系统下,
int的最大值(INT_MAX)是 。
显然, 亿远超了 亿的上限,发生了整数溢出。
2.2 二进制底层运算
让我们从二进制的角度看这个过程(假设 32 位 int):
- 的十六进制表示为
0x0000FFFF。 - 的完整 64 位结果为:
0x00000000FFFE0001。 - 截断(Truncation):由于变量
y只有 32 位,高位的0被舍弃,只保留低 32 位:0xFFFE0001。
2.3 补码解析
在计算机中,有符号整数使用 补码 (Two’s Complement) 存储:
0xFFFE0001的最高位是1,表示这是一个 负数。- 求原值:对补码取反加 1。
0xFFFE0001取反0x0001FFFE- 加 1
0x0001FFFF
- 十六进制
0x1FFFF转换为十进制正是131071。 - 结合符号位,结果就是
-131071。
3. 标准 C 语言的定义
未定义行为 (Undefined Behavior)根据 C 语言标准,有符号整数的溢出是“未定义行为”。 这意味着编译器可以选择任何处理方式(报错、停止运行、或者像本例中这样简单的截断)。在大多数现代编译器和 CPU 架构(如 x86/ARM)上,溢出通常遵循补码回绕的逻辑,但在编写高可靠性代码时绝对不能依赖这一行为。
4. 防坑指南
4.1 使用更大范围的类型
如果预测结果会超过 21 亿,应使用 long long(至少 64 位):
long long x = 65535;long long y = x * x; // 结果为 42948362254.2 强制类型转换
如果 x 已经是 int 且无法修改,在运算时必须强制转换,否则中间结果仍会溢出:
int x = 65535;long long y = (long long)x * x; // 确保乘法在 64 位下执行4.3 编译选项
可以使用编译器的安全检查开关(如 GCC 的 -ftrapv),在发生有符号溢出时直接让程序崩溃,而不是带着错误的数据继续运行。
总结
- 65535 的平方 超过了 32 位有符号整数的极限。
- 溢出后的二进制位被截断为
0xFFFE0001。 - 该十六进制值在补码体系下恰好代表 -131071。
- 永远不要假设
int能装下你的野心。
[编程] C 语言陷阱:整数平方溢出
https://www.eustia-astraea.top/posts/programming/c-integer-overflow-trap/