是的,Go for range 中 &v 总指向同一地址,因循环变量 v 被复用,每次仅赋新值;安全做法是用索引取址(&s[i])或显式复制(val := v; &val)。
Go for 循环中
总是指向同一个地址?
是的,这是 Go 语言最常被踩的坑之一:在
中取变量地址(比如
),所有迭代项拿到的其实是同一个变量
的地址,而不是每个元素的独立地址。
根本原因在于:Go 的
复用循环变量
,每次迭代只是把新值拷贝进它,
本身的内存位置不变。所以
始终返回同一块栈地址。
常见错误现象:
在循环里打印出一串相同的指针值
典型使用场景:构建
、往 goroutine 传地址、缓存结构体字段指针
后果:所有指针最终都指向最后一次迭代的值(比如全变成最后一个元素)
怎么安全地获取每个元素的真实地址?
必须绕过复用变量,让每个迭代有独立作用域或显式副本。
对切片/数组:用索引访问 + 取址 ——
,不是
对 map:不能直接取
,因为
是 copy 值;改用
再取址(前提是 map 存的是可寻址类型,如 struct 或指针)
通用兜底法:在循环体内声明新变量并赋值,再取它的地址 ——
注意:如果原元素本身不可寻址(比如 map value、函数返回值、字面量),
也无效,此时只能复制后取址
示例:
立即学习
“
go语言免费学习笔记(深入)
”;
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
错例:
goroutine 里传
为什么总得到最后一个值?
这是上一问题的高危变种:循环启动 goroutine 时捕获了复用变量
的地址,而 goroutine 实际执行时循环早已结束,
里只剩最后一次赋的值。
现象:并发处理切片,结果全打印/写入最后一个元素内容
根本原因:闭包捕获的是变量
的引用,不是值;且
地址不变、值被覆盖
正确做法:要么用索引传值(
),要么显式绑定当前值(
)
别依赖
“修复”——这只是掩盖竞态,不是解决
struct 字段地址和
一起用会怎样?
如果 range 的是 struct 切片,且你取
,依然会掉进复用陷阱 ——
是复用的,
是它的字段,但
还是基于那个固定地址计算出来的偏移,最终所有指针仍指向同一块内存里的字段位置。
即使
是 struct 类型,
和
都不安全
安全做法:用索引访问
,或者先复制
再取字段地址(
)
注意:如果 struct 包含指针字段,复制
会复制指针值,但不会复制它指向的内容 —— 这是语义问题,不是地址陷阱
这事没捷径,只要涉及取址,就得盯住变量是不是被复用了。Go 不会在语法层帮你隔离每次迭代的变量生命周期,得自己动手。
addrfor range&vvrangevv&vfmt.Printf("%p", &v)[]*string&s[i]&v&vvmap[key]val := v; ptr := &val&s[i]for i := range s {
ptr := &s[i] // ✅ 安全:每次取不同元素地址
// ...
}for _, v := range s {
ptr := &v // ❌ 危险:所有 ptr 指向同一地址
// ...
}&vvvvvgo f(s[i])go func(val string) { ... }(v)time.Sleeprange&v.Fieldvv.Field&v.Fieldv&v&v.Field&s[i].Fieldvtmp := v; &tmp.Fieldv