
本文澄清 Go 函数中传递结构体指针时常见的地址误解:&u 获取的是指针变量自身的栈地址(每次调用独立),而 u 才是实际指向堆/栈中 User 实例的地址,该地址在多次调用中保持不变。
本文澄清 go 函数中传递结构体指针时常见的地址误解:`&u` 获取的是指针变量自身的栈地址(每次调用独立),而 `u` 才是实际指向堆/栈中 `user` 实例的地址,该地址在多次调用中保持不变。
在 Go 中,函数参数始终是值传递,即使你传递的是指针(如 *User),传递的也是该指针值的一个副本。这意味着:
- u 是一个 *User 类型的变量,它本身存储的是某个 User 实例的内存地址(例如 0x10434120);
- &u 则是这个指针变量 u 在当前函数栈帧中的自身地址——它位于调用栈上,每次函数调用都会在栈上分配新的局部变量空间,因此 &u 的值必然不同。
观察原始代码输出:
main() address: 0x1040a120 0x10434120 Modify() address: 0x1040a130 0x10434120 Modify() address: 0x1040a138 0x10434120 ...
- 第二列(&u.Name)始终为 0x10434120 → 表明 User 实例本身未被重建,其字段内存地址恒定;
- 第一列(&u)递增(0x1040a130 → 0x1040a138 → 0x1040a140)→ 这是每次 Modify 调用时,参数 u(即 *User 类型的局部变量)在栈上的新位置,间隔 8 字节,正对应一个指针在 64 位系统下的大小(8 字节);
- 首次 main() 中 &u 为 0x1040a120,与后续 Modify 中 &u 的起始地址相差 16 字节,是因为 main 函数栈帧与 Modify 栈帧布局不同(含返回地址、保存寄存器等开销),属于正常栈对齐行为,并非创建了新对象。
✅ 正确验证方式:打印 u(指针值)而非 &u(指针变量地址):
func Modify(u *User) {
u.Name = "Paul11"
fmt.Printf("Modify() instance addr: %p, Name field addr: %p\n", u, &u.Name)
}
func Modify2(u *User) {
u.Name = "Leto"
fmt.Printf("Modify2() instance addr: %p, Name field addr: %p\n", u, &u.Name)
}运行后将看到:
main() instance addr: 0x10434120, Name field addr: 0x10434120 Modify() instance addr: 0x10434120, Name field addr: 0x10434120 Modify() instance addr: 0x10434120, Name field addr: 0x10434120 ...
所有 u 值完全一致 —— 证明始终操作的是同一个 User 实例,没有“每次 mutation 创建新对象”这回事。
⚠️ 关键总结:
- Go 中结构体字段的地址在其生命周期内固定不变(除非被移动,如切片扩容触发内存拷贝,但此处不适用);
- &u 是栈上指针变量的地址,随调用栈变化,不可用于判断对象是否新建;
- 若需确保不意外修改原对象,应显式复制:uCopy := *u; uCopy.Name = ... 或使用值接收者;
- 性能提示:频繁取 &u 并无实际意义,真正关心的是 u 指向的地址是否稳定——本例中完全稳定。
理解这一机制,是写出高效、可预测 Go 代码的基础。

















