
go 的 for-range 循环会复制元素值,若对循环变量取地址并存入切片,所有指针将指向同一内存位置(即最后一次迭代的副本),导致意外的数据覆盖。
go 的 for-range 循环会复制元素值,若对循环变量取地址并存入切片,所有指针将指向同一内存位置(即最后一次迭代的副本),导致意外的数据覆盖。
在 Go 中,for _, e := range b 语句中的 e 是一个每次迭代都重新赋值的局部变量,而非对数组元素的引用。它始终持有当前元素的副本,其内存地址在整个循环中保持不变——这意味着 &e 始终返回同一个地址,而该地址上存储的值会在每次迭代中被覆盖。当循环结束时,e 的最终值为 Foo{3},因此所有通过 &e 获取的指针都指向这个已被多次写入的临时变量,最终打印出 {0} {3} {3} {3}。
正确做法:取原始元素的地址
要获取每个数组元素的真实地址,应直接对源数组索引取址:
for i := range b {
a = append(a, &b[i])
}完整修正后的代码如下:
package main
import "fmt"
type Foo struct {
val int
}
func main() {
var a = make([]*Foo, 1)
a[0] = &Foo{0}
var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
for i := range b {
a = append(a, &b[i]) // ✅ 正确:取数组元素地址
}
for _, e := range a {
fmt.Printf("%v ", *e)
}
// 输出:{0} {1} {2} {3}
}关键原理说明
- Go 没有“引用类型”(如 C++ 或 Java 中的引用),只有值类型和指针类型;
- e 是 Foo 类型的普通变量,每次迭代执行 e = b[i](值拷贝);
- &e 取的是这个局部变量的地址,不是 b[i] 的地址;
- 所有 &e 指向同一栈地址,内容随循环不断更新,最终固化为末次值。
其他安全替代方案
若需在循环中动态构造结构体并取址,可显式声明新变量避免歧义:
for _, e := range b {
temp := e // 显式拷贝,确保每次有独立地址
a = append(a, &temp)
}⚠️ 注意:此方式虽能工作,但会额外分配内存(每次创建新变量),性能略低于直接索引取址;且易被误认为“解决了问题”,实则掩盖了根本逻辑——应优先使用 &b[i] 这一明确、高效、无副作用的方式。
总结:在 Go 中对 range 循环变量取地址是典型反模式。牢记——range 提供的是值副本,如需原始数据地址,请始终通过索引访问容器元素。


















