
go 的 for-range 循环复用迭代变量,若直接对循环变量取地址并返回指针,将始终指向最后一次迭代的值——这是因变量内存复用导致的常见陷阱,需改用切片索引取址来确保准确性。
go 的 for-range 循环复用迭代变量,若直接对循环变量取地址并返回指针,将始终指向最后一次迭代的值——这是因变量内存复用导致的常见陷阱,需改用切片索引取址来确保准确性。
在 Go 中,for _, record := range records 语句中的 record 并非每次迭代都创建新变量,而是同一个栈变量在每次迭代中被重新赋值。这意味着所有对 &record 的取址操作,实际上都指向同一块内存地址。当循环结束时,该地址中存储的是最后一次迭代的值(即 Record{3, "Third"}),因此即使逻辑上匹配了 Id == 2,返回的指针也指向已被覆盖为 "Third" 的 record 变量。
以下是最小复现示例:
for _, record := range records {
if record.Id == 2 {
fmt.Printf("Inside loop: %p → %+v\n", &record, record) // 地址相同,但值随迭代变化
}
}
// 最终 &record 指向的是 {3, "Third"} 的副本✅ 正确做法是:通过索引访问原切片元素并取其地址,因为切片底层数组的元素具有稳定内存位置:
for i, record := range records {
if record.Id == 2 {
foundRecord = &records[i] // ✅ 安全:指向原始切片第 i 个元素
break
}
}⚠️ 注意事项:
- 此问题仅影响取循环变量地址的场景,普通值拷贝(如 record.Name)完全安全;
- 若切片后续发生扩容(如追加大量元素),原有元素地址可能失效——但只要不修改切片本身,&records[i] 在函数生命周期内始终有效;
- 该行为是 Go 显式设计的性能优化(避免每轮分配新变量),并非 bug,官方 FAQ 中明确说明(Closures and goroutines);
- 在闭包或 goroutine 中捕获循环变量时,同样适用此规则——应显式拷贝值(id := record.Id)或使用索引。
总结:在 Go 中,永远不要返回 &record(循环变量地址),而应返回 &slice[i](切片元素地址)。理解变量复用机制,是写出健壮 Go 代码的关键一步。


















