闭包捕获的是变量本身而非值,Go中闭包通过指针访问外层变量,导致多个闭包共享同一内存地址;循环中创建闭包时若未正确处理,易因变量被覆盖而引发意外行为。

闭包捕获的是变量本身,不是值
Go 闭包访问外层变量时,拿的是变量的内存地址,相当于隐式持有了指针。哪怕你没写 *p 或 &x,只要闭包里读或写了那个变量,Go 就会把它“逃逸”到堆上,并让闭包结构体保存指向它的指针。所以多个闭包可能共享同一块内存 —— 改一个,另一个读到的就是新值。
循环中建闭包,最容易暴露引用问题
常见错误写法:
for i := 0; i go func() { fmt.Println(i) }()
}
输出往往是 3 3 3,因为所有闭包都捕获了同一个变量 i 的地址,等 goroutine 真正执行时,循环早已结束,i 已是 3。解决办法不是加指针,而是让每次迭代绑定独立副本:
- 用局部变量中转:for i := 0; i
- 把 i 当参数传进闭包:go func(n int) { fmt.Println(n) }(i)
显式用指针时,行为更可控但风险更高
如果你在外层定义了一个指针 p := &x,再在闭包里用 *p,那闭包捕获的是 p 这个指针变量本身(地址),不是 x。这意味着:
- 如果 p 后续被重新赋值(比如指向另一个变量),闭包里 *p 读到的就是新目标的值
- 如果 x 是栈变量且没逃逸,而你又把 &x 传给了闭包,Go 编译器会强制让它逃逸到堆,否则运行时可能 panic
底层其实就是一个带指针的结构体
每个闭包在运行时都是一个结构体:funcval + *capture。其中 *capture 指向一块堆内存,里面按顺序存着所有被捕获变量的值(或它们的地址)。访问时靠固定偏移直接取,没有多层解引用。所以 Go 闭包高效,但也意味着:你看到的“变量”,背后大概率已是堆上某个地址的别名。
基本上就这些。理解“捕获即引用”,就能避开八成闭包坑。










