是的,Go for range 中 &v 总指向同一地址,因循环变量 v 被复用,每次仅赋新值;安全做法是用索引取址(&s[i])或显式复制(val := v; &val)。

Go for 循环中 addr 总是指向同一个地址?
是的,这是 Go 语言最常被踩的坑之一:在 for range 中取变量地址(比如 &v),所有迭代项拿到的其实是同一个变量 v 的地址,而不是每个元素的独立地址。
根本原因在于:Go 的 range 复用循环变量 v,每次迭代只是把新值拷贝进它,v 本身的内存位置不变。所以 &v 始终返回同一块栈地址。
- 常见错误现象:
fmt.Printf("%p", &v)在循环里打印出一串相同的指针值 - 典型使用场景:构建
[]*string、往 goroutine 传地址、缓存结构体字段指针 - 后果:所有指针最终都指向最后一次迭代的值(比如全变成最后一个元素)
怎么安全地获取每个元素的真实地址?
必须绕过复用变量,让每个迭代有独立作用域或显式副本。
- 对切片/数组:用索引访问 + 取址 ——
&s[i],不是&v - 对 map:不能直接取
&v,因为v是 copy 值;改用map[key]再取址(前提是 map 存的是可寻址类型,如 struct 或指针) - 通用兜底法:在循环体内声明新变量并赋值,再取它的地址 ——
val := v; ptr := &val - 注意:如果原元素本身不可寻址(比如 map value、函数返回值、字面量),
&s[i]也无效,此时只能复制后取址
示例:
立即学习“go语言免费学习笔记(深入)”;
for i := range s {
ptr := &s[i] // ✅ 安全:每次取不同元素地址
// ...
}
错例:
for _, v := range s {
ptr := &v // ❌ 危险:所有 ptr 指向同一地址
// ...
}
goroutine 里传 &v 为什么总得到最后一个值?
这是上一问题的高危变种:循环启动 goroutine 时捕获了复用变量 v 的地址,而 goroutine 实际执行时循环早已结束,v 里只剩最后一次赋的值。
- 现象:并发处理切片,结果全打印/写入最后一个元素内容
- 根本原因:闭包捕获的是变量
v的引用,不是值;且v地址不变、值被覆盖 - 正确做法:要么用索引传值(
go f(s[i])),要么显式绑定当前值(go func(val string) { ... }(v)) - 别依赖
time.Sleep“修复”——这只是掩盖竞态,不是解决
struct 字段地址和 range 一起用会怎样?
如果 range 的是 struct 切片,且你取 &v.Field,依然会掉进复用陷阱 —— v 是复用的,v.Field 是它的字段,但 &v.Field 还是基于那个固定地址计算出来的偏移,最终所有指针仍指向同一块内存里的字段位置。
- 即使
v是 struct 类型,&v和&v.Field都不安全 - 安全做法:用索引访问
&s[i].Field,或者先复制v再取字段地址(tmp := v; &tmp.Field) - 注意:如果 struct 包含指针字段,复制
v会复制指针值,但不会复制它指向的内容 —— 这是语义问题,不是地址陷阱
这事没捷径,只要涉及取址,就得盯住变量是不是被复用了。Go 不会在语法层帮你隔离每次迭代的变量生命周期,得自己动手。










