&v 总是返回相同地址,因为 for range 中的迭代变量 v 是复用的单一栈变量,每次迭代仅覆盖其值而非重新分配内存,故地址恒定。
因为 for range 中的迭代变量(如 v)是复用的单一栈变量,不是每次迭代新建的。 它的地址从循环开始就固定,值只是被不断覆盖——所以 &v 每次都返回同一个内存地址。
为什么 &v 总是返回相同地址?
Go 编译器在生成 for range 代码时,会预先声明一个临时变量(比如 v),并在每次迭代中执行类似 v = slice[i] 的赋值。这个变量只分配一次栈空间,生命周期覆盖整个循环。你看到的“不同值”,只是同一块内存被反复写入的结果。
- 这不是 bug,是明确设计:减少频繁栈分配开销
- 对值类型(
int、struct)尤其明显;对指针或接口类型,复用的是指针/接口变量本身,不是它指向的内容 - 用
fmt.Printf("%p", &v)打印就能验证:三行输出地址完全一致
常见踩坑场景:保存 &v 到切片或 map
典型错误是把 &v 存进 []*T 或 map[string]*T,结果所有指针都指向最后一个值。
- 错误写法:
for _, v := range items { ptrs = append(ptrs, &v) }→ptrs里全是同一地址 - 错误写法:
for _, v := range items { m[v.ID] = &v }→m中所有键对应的值最终都是最后一个v - 根本原因:循环结束时,
v里存的是最后一次迭代的值,所有&v都指向它
正确获取每个元素独立地址的两种方式
关键是要绕过迭代变量复用机制,让每个地址对应一个独立的内存位置。
- 用索引直接取址:
&items[i]—— 安全,但要求items生命周期足够长(不能是局部切片的返回值) - 显式拷贝再取址:
vCopy := items[i]; &vCopy—— 创建新变量,有独立栈空间,适合需要脱离原切片生命周期的场景 - 避免写成
for i, v := range items { &items[i] }这种冗余形式;直接for i := range items更清晰
最易被忽略的一点:这个问题不会在单纯读取 v 时暴露,只有当你对 v 取地址并长期持有(存入集合、传给 goroutine、返回指针)时才爆发。一旦涉及地址,必须主动判断变量是否复用。


















