go默认不检查整数溢出,int加至最大值后会回绕而非panic;这是性能优先的设计取舍,需程序员显式检查,如a>math.maxint64-b时返回错误。

Go 默认不检查整数溢出,int 加到最大值后继续加会回绕
Go 编译器在默认构建模式下对整数算术不做运行时溢出检查——int64 加 1 超过 math.MaxInt64,结果直接变成 math.MinInt64,不会 panic,也不会报错。这和 Rust 的 +(panic)或 Python 的自动大整数不同,是 Go 明确的设计取舍:性能优先、错误由程序员显式兜底。
常见错误现象:for i := 0; i 中 <code>n == math.MaxInt64,循环永远不终止;或者 ID 生成器用 counter++ 累加后突然变负,下游解析失败但无日志提示。
- 所有内置整数类型(
int、int8…uint64)都遵循该行为 -
go build -gcflags="-d=checkptr"这类调试标志不影响算术溢出检查 - 只有启用
go run -gcflags="-d=ssa/check/on"(仅限调试)才可能触发部分 SSA 层溢出诊断,但不可用于生产
用 math 包的 add/sub/mul 函数做安全运算
标准库 math 包从 Go 1.21 开始提供 add、sub、mul 等函数(位于 math/bits 的补充逻辑中),但注意:它们**不在 math 主包,而在 math 子模块 math/bits 的配套工具里**——实际需手动实现或引入第三方,比如官方推荐的 golang.org/x/exp/constraints + 自定义封装,或更直接地用 github.com/cockroachdb/apd(高精度)或 github.com/zeebo/xxh3(仅哈希场景)。
更现实的做法是自己写薄封装:
立即学习“go语言免费学习笔记(深入)”;
func SafeAdd(a, b int64) (int64, bool) {
if b > 0 && a > math.MaxInt64-b {
return 0, false
}
if b < 0 && a < math.MinInt64-b {
return 0, false
}
return a + b, true
}- 别依赖
unsafe或反射做“通用整型”检查——Go 没有泛型约束下的统一溢出检测接口 - 对
uint类型,只检查上界(a > math.MaxUint64 - b),无需考虑负数分支 - 如果已在用
go 1.22+,可配合constraints.Integer写泛型版,但注意类型推导开销对热点路径的影响
构建时开启 -gcflags="-d=checkoverflow" 只能捕获常量溢出
这个 flag 看起来像开关,但实际作用非常有限:它只在编译期对**字面量表达式**做检查,比如 const x = 1 会报错,而 <code>var a int64 = 1 运行时照样回绕。它不是运行时防护,也不能覆盖变量参与的动态计算。
- 无法检测
for循环中的索引溢出、切片长度累加、时间戳差值计算等典型场景 - CI 中加这个 flag 只能防住低级笔误,不能替代逻辑校验
- 与
GOARCH=wasm或tinygo无关——那些环境另有自己的溢出策略
关键数据结构字段用 int64 配合前置校验,别迷信 uint64
很多人以为换成 uint64 就能避免负值问题,其实只是把溢出点从 -9223372036854775808 推迟到 18446744073709551615,且一旦溢出,uint64 会归零(0),比负数更难被发现——比如计数器从 18446744073709551615 变成 0,下游可能当成初始化状态重置逻辑。
- ID、版本号、时间戳差值等字段,优先选
int64,并在赋值/更新前调用SafeAdd类函数校验 - 数据库主键映射到 struct 时,如果 DB 是
BIGINT SIGNED,Go 侧必须用int64,否则溢出后符号位错乱 - HTTP query 参数解析(如
?limit=9223372036854775808)要用strconv.ParseInt(s, 10, 64)并检查 error,而不是strconv.Atoi(默认 int,平台相关)
实际项目里最易被忽略的,是那些看似“不可能溢出”的中间计算:比如两个 time.Unix() 时间戳相减得秒数,再乘 1000 得毫秒——当时间跨度超 292 年时,int64 就撑不住。这种地方不写校验,线上跑几年才暴露。










