range中v地址总相同是因为迭代变量v是单个栈变量,循环中只分配一次内存,每次仅重新赋值;直接取&v会导致所有指针指向同一地址,最终数据被覆盖为最后一次迭代值。

为什么range里的v地址总是一样
因为range语句中声明的迭代变量(比如v)是单个栈变量,整个循环只分配一次内存。每次迭代只是给它重新赋值,&v拿到的永远是同一块地址。这不是 bug,是 Go 的确定性行为——你打印&v会发现所有迭代输出的指针值完全相同。
直接取&v会导致什么问题
所有指针指向同一内存位置,最终数据被覆盖为最后一次迭代的值。常见场景包括:
- 构建
[]*T切片时,所有元素都指向同一个v,读出来全是最后一个值 - 传给
http.HandleFunc或time.AfterFunc等延迟执行函数,回调里读到的是终值 - 在
map[string]*T中存&v,所有 key 对应的指针实际指向同一地址
两种安全写法必须二选一
不能跳过隔离步骤,以下任一方式都有效,语义清晰且编译器可优化:
-
显式按索引取地址:
for i := range slice { p := &slice[i] }—— 直接对底层数组/切片元素取址,每次都是独立地址 -
循环内重声明变量:
for _, v := range slice { v := v; p := &v }——v := v创建新绑定,Go 保证每次迭代的v有独立生命周期(即使栈空间复用,语义上也隔离)
别混用:比如go func(v T) { ... }(v)是传值,但若后续要取地址,仍得基于这个参数再做&v;而&v本身仍是安全的,因为参数是独立副本。
立即学习“go语言免费学习笔记(深入)”;
容易被忽略的延迟执行场景
这个陷阱不只发生在goroutine里。任何闭包延迟执行都适用:
-
defer func() { fmt.Println(v) }()—— 这个没问题,因为defer同步执行,v还是当前迭代的值 -
defer func() { go func() { fmt.Println(v) }() }()—— 里面的goroutine仍然捕获终值,必须对v做隔离 -
cron.AddFunc("@every 1m", func() { process(v) })—— 同理,v需提前拷贝
真正危险的不是并发本身,而是「变量生命周期 > 闭包执行时机」——只要存在时间差,就得手动冻结值。


















