704 字
4 分钟
[编程] C 语言陷阱:整数平方溢出

在处理数值运算时,如果不关注类型的取值范围,即使是简单的平方运算也会产生令人困惑的结果。

1. 现象描述#

观察以下代码片段:

int x = 65535;
int y = x * x;
printf("%d\n", y);
运行结果

在大多数 32 位系统下,输出结果不是 4294836225,而是 -131071

为什么一个正数的平方会变成负数?


2. 深度原因分析#

问题的核心在于 32 位有符号整数(int)的溢出 以及 计算机底层的补码表示法

2.1 数值大小对比#

  • 理论结果655352=4,294,836,22565535^2 = 4,294,836,225
  • int 类型的上限:在 32 位系统下,int 的最大值(INT_MAX)是 2311=2,147,483,6472^{31} - 1 = 2,147,483,647

显然,42.942.9 亿远超了 21.421.4 亿的上限,发生了整数溢出

2.2 二进制底层运算#

让我们从二进制的角度看这个过程(假设 32 位 int):

  1. 6553565535 的十六进制表示为 0x0000FFFF
  2. 65535×6553565535 \times 65535 的完整 64 位结果为:0x00000000FFFE0001
  3. 截断(Truncation):由于变量 y 只有 32 位,高位的 0 被舍弃,只保留低 32 位:0xFFFE0001

2.3 补码解析#

在计算机中,有符号整数使用 补码 (Two’s Complement) 存储:

  • 0xFFFE0001 的最高位是 1,表示这是一个 负数
  • 求原值:对补码取反加 1。
    • 0xFFFE0001 取反 \rightarrow 0x0001FFFE
    • 加 1 \rightarrow 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; // 结果为 4294836225

4.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/
作者
mcsl
发布于
2024-01-13
许可协议
CC BY-NC-SA 4.0