Go闭包捕获变量地址而非值,导致循环中多个闭包共享同一变量;for循环末尾i值固定,故所有闭包输出最后一个i值,属引用共享典型问题。

Go 闭包捕获的是变量的内存地址,不是值快照——这意味着所有共享该变量的闭包,看到的是同一个变量在执行时刻的最新值。
for 循环中闭包全输出最后一个值?不是 bug,是引用共享
常见错误现象:for i := 0; i 执行后输出 <code>3 3 3。
根本原因:Go 不为每次循环迭代创建新变量,i 在整个 for 块作用域内只有一份内存地址。所有闭包都绑定了这个地址,等真正执行时,i 已经变成 3。
- 正确做法一:用短变量声明制造独立绑定 ——
for i := 0; i - 正确做法二:通过参数传值,在注册闭包时立即求值 ——
for i := 0; i - range 遍历同理:
v是复用的,不能直接在闭包里用;必须写成v := v或作为参数传入
defer 中闭包读的是执行时刻的变量值
常见翻车:for i := 0; i 输出 <code>3 3 3,而非预期的 0 1 2。
立即学习“go语言免费学习笔记(深入)”;
原因:defer 注册时不读 i,而是在函数返回前统一执行——此时 i 已完成循环,值为 3。
- 想冻结注册时的值,必须显式复制:
for i := 0; i - 混合写法陷阱:
defer func(x int) { fmt.Println(x, i) }(i)——x是注册时值,i是执行时值,容易混淆 - 命名返回值会被 defer 修改:
func foo() (x int) { x = 1; defer func() { x = 2 }(); return }实际返回2
闭包延长变量生命周期,容易拖住大对象
只要闭包值还被引用,它捕获的变量就无法被 GC 回收。这不是泄漏,但可能造成意料之外的内存驻留。
典型风险场景:http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { use(r.Body) }) → 整个 *http.Request 可能长期驻留。
- 排查方法:
go tool compile -m查看逃逸分析,确认哪些变量被抬升到堆 - 安全做法:缩小闭包捕获范围,比如只传
r.URL.Path而非整个r - 避免捕获大
struct、大slice;若必须用,考虑拷贝关键字段或用指针解引用后再传
最易被忽略的一点:这种内存滞留没有 panic、没有报错,只表现为服务运行越久内存越高,且 pprof 里找不到明显泄漏点。


















