有符号整数溢出是未定义行为,编译器可任意优化甚至删除逻辑;无符号溢出是明确定义的模回绕。乘法、指针运算、数组索引易引发隐蔽溢出,需用固定宽度类型、避免混用符号类型、关键处调用溢出检查函数。

有符号整数溢出是未定义行为,不能依赖任何具体结果;无符号整数溢出是明确定义的模回绕。
有符号整数溢出:编译器可以随便处理
比如 int32_t x = 2147483647; x++;,标准只说这是“未定义行为”(UB),不保证变成 -2147483648。实际中常见结果是 -2147483648,但优化开启后,编译器可能直接删掉检查逻辑、生成错误分支,甚至让整个函数提前返回。
- Clang/GCC 在
-O2下可能把if (x > 0 && x + 1 这类判断优化掉,因为它假定有符号溢出永不发生 - 调试时看着正常,上线后行为突变,尤其在不同 CPU 架构或编译器版本间差异明显
- 用
clang++ -fsanitize=signed-integer-overflow可在运行时报错,适合开发阶段捕获
无符号整数溢出:结果可预测,但未必符合直觉
uint32_t y = 4294967295U; y++; 必然得到 0,因为标准强制要求按 2^32 取模。这不是 bug,是设计行为。
- 适用于计数器、哈希索引、环形缓冲区等需要自动回绕的场景
- 但若你本意是“检测上限”,却用了
uint32_t,那y == 0就不是错误信号,而是自然状态 - 和有符号类型混用时危险:比如
for (uint32_t i = n; i >= 0; --i)会无限循环,因为i永远不会小于 0
乘法和数组索引溢出最容易踩坑
算术溢出不只发生在 ++/--,乘法和指针运算更隐蔽。
-
int a = 50000, b = 50000; int c = a * b;→ 结果不是 2500000000,而是未定义值(常见为 -1794967296) -
vector<int> v(10); size_t idx = 15; v[idx] = 1;</int>→ 如果idx来自计算(如i * j),溢出后可能越界访问 - 分配内存前务必检查:
size_t total = count * sizeof(T); if (total / sizeof(T) != count) { /* overflow */ }
怎么写才不容易出错
别靠“试试看”或“我平台下跑得通”,要从类型选择和检查逻辑入手。
- 明确数据范围:输入最大值 × 系数是否超
int32_t?超了就用int64_t或先转成uint64_t做乘法 - 避免混合使用有/无符号:比较、循环、容器索引统一用
size_t或带符号类型,别让隐式转换偷偷改语义 - 关键路径加保护:用
std::add_overflow(C++23)、__builtin_add_overflow(GCC/Clang)或手动检查边界 - 竞赛或嵌入式里,优先用固定宽度类型(
int32_t、uint16_t),别依赖int是 32 位
最常被忽略的是:溢出本身不抛异常、不中断、不报错,它静默地把一个正数变成负数、把一个大数变成小数、把一个索引变成非法地址——而你的 if 判断还照常执行。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











