for range 中的 v 是复用变量,每次迭代仅拷贝值到同一栈地址;直接取 &v 会导致所有指针指向最后一个值,安全做法是用索引取址 &slice[i] 或显式拷贝 val := slice[i]; &val。

for range 中的 v 是复用变量,不是每次新建
Go 的 for range 在遍历切片、数组或字符串时,v(即 value 变量)在循环开始前就声明好,整个循环只用这一块栈内存。每次迭代只是把新元素的值拷贝进去,&v 拿到的永远是这块固定地址——不是 bug,是编译器明确设计的优化行为,避免频繁分配栈空间。
直接写 &v 会导致所有指针指向最后一个值
常见错误现象包括:
- 把
&v存进切片或 map,结果所有元素都变成最后一个遍历值 - 传给 goroutine 后,所有协程读到的都是循环结束时
v的最终值 - 打印
&v地址,三次迭代输出完全相同的十六进制地址(如0xc000014088)
根本原因:你存的不是“第 i 个元素的地址”,而是“那个被反复赋值的临时变量的地址”。
安全取地址的两种写法及适用场景
必须绕过 v 的复用机制。推荐以下任一方式:
-
用索引取原切片元素地址:
&slice[i]—— 速度快,但要求原切片生命周期足够长(不能是函数返回的局部切片) -
显式拷贝再取址:
val := slice[i]; &val—— 多一次值拷贝,但生成独立栈变量,生命周期由当前作用域保证,更安全
示例对比:
// ❌ 危险
for _, v := range items {
ptrs = append(ptrs, &v) // 全是指向同一地址
}
// ✅ 安全(推荐)
for i := range items {
val := items[i]
ptrs = append(ptrs, &val)
}
别忽略:map 和 channel 遍历时也存在同样问题
虽然 map 和 channel 的底层实现不同,但 for range 对 value 变量的处理逻辑一致——v 仍是复用变量。所以:
- 遍历
map[string]struct{}并保存&v到切片?同样全是指向同一地址 - 往 goroutine 里传
&v?所有 goroutine 共享同一个变量副本
只要用了 for _, v := range xxx 这种形式,且对 v 取了地址,就必须警惕复用陷阱——它不挑数据结构,只认语法模式。











