闭包捕获变量地址而非值,导致循环中所有闭包共享同一变量地址,最终都输出终值。

闭包捕获的是变量地址,不是值;只要闭包还活着,它引用的变量就无法被 GC 回收——哪怕外层函数早返回了。
for 循环中闭包共享同一变量地址导致输出终值
这是最常踩的坑:所有匿名函数都读取同一个循环变量的地址,执行时看到的是最后一次赋值的结果。
for i := 0; i → 全部输出 <code>3- 根本原因不是“延迟执行”,而是
&i始终指向同一块栈内存 - 修复必须切断复用链:显式声明新变量
val := i; go func() { fmt.Println(val) }(),或通过参数传值go func(v int) { fmt.Println(v) }(i) - 验证是否生效?加日志:
log.Printf("i addr: %p, val addr: %p", &i, &val),你会看到&i恒定,&val每次不同
闭包作为返回值或传入 goroutine 导致变量逃逸到堆
编译器通过逃逸分析静态判断变量是否“可能被闭包外持有”。一旦闭包被返回、传给 go、存入全局 map 或 interface{},它捕获的变量大概率逃逸。
-
func() func() int { x := 42; return func() int { return x } }→x逃逸 -
go func() { _ = x }()→x极大概率逃逸 - 验证方式必须是:
go build -gcflags="-m -m"(单个-m不显示变量级逃逸) -
&x escapes to heap不等于“一定逃逸”,要看上下文;真正决定逃逸的是“地址是否泄露”,不是取地址动作本身
闭包长期持有大对象引发内存泄漏
闭包隐式持有所引用的所有外部变量。若闭包被注册为 HTTP handler、存入全局 map、或启动后台 goroutine,而它又无意中捕获了 *http.Request、context.Context、几 MB 的 []byte,那这块内存就卡住不释放。
立即学习“go语言免费学习笔记(深入)”;
-
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _ = r.Body })→ 整个*http.Request长期滞留 -
var handlers map[string]func()存几十个闭包,每个捕获不同user对象 → 忘记delete(handlers, key),user永远不会被回收 - 排查方法:
go tool pprof --inuse_space查看堆中长期存活的大对象,再结合源码定位是否由闭包持有 - 风险点:这种泄漏无声无息——没有 panic,日志没报错,只表现为内存缓慢上涨、GC pause 时间逐渐变长
最隐蔽的是 defer 中的闭包:命名返回值被 defer func() { x = 2 }() 修改时,该变量生命周期会绑定到 defer 执行完成那一刻;如果它是大结构体或含指针字段,也会因此逃逸到堆。


















