Go语言中函数返回指针时的地址理解与常见误区

本文详解go函数返回指针时为何`&i`打印结果不一致——关键在于混淆了“指针变量自身的地址”与“指针所指向的地址”,通过对比c语言行为,厘清go内存模型中的指针语义。

在Go中,当你从函数返回一个指针(如 *int),实际返回的是指向堆(或逃逸分析后分配位置)上某个整数的内存地址值。但初学者常犯的一个典型错误,是误将“接收该指针的变量的地址”(即 &i)当作“指针所指向的目标地址”来打印和比较。

回顾你的代码:

func createPointerToInt() *int {
    i := new(int) // 分配一个int,返回指向它的指针(例如:0x1040a128)
    fmt.Println(&i) // ❌ 打印的是局部变量 i(指针类型)自身的地址,不是它指向的地址!
    return i
}

func main() {
    i := createPointerToInt() // i 是 *int 类型,其值为 0x1040a128
    fmt.Println(&i)           // ❌ 再次打印 main 中变量 i 自身的地址(如 0x1040a120)→ 两个栈变量地址不同,差8字节属正常(64位系统下指针占8字节,相邻栈变量偏移合理)
}

⚠️ 关键点解析:

  • i 在 createPointerToInt 中是一个局部指针变量,new(int) 返回的地址被赋给它;&i 取的是这个局部变量在栈上的地址(函数返回后该变量已销毁);
  • main 中的 i 是另一个独立的指针变量,它接收了返回的地址值(如 0x1040a128),而 &i 是这个新变量在 main 栈帧中的地址;
  • 因此 &i 在两处打印的是两个不同栈变量的地址,自然不同——这与C中 printf("%#08x\n", r) 正确打印指针值(而非 &r)形成鲜明对比。

✅ 正确做法:要验证指针是否成功传递目标地址,应直接打印指针变量本身(即地址值),而非其地址:

func createPointerToInt() *int {
    i := new(int)
    *i = 42
    fmt.Printf("Inside: pointer value = %p, pointed value = %d\n", i, *i)

return i } func main() { p := createPointerToInt() fmt.Printf("Outside: pointer value = %p, pointed value = %d\n", p, *p) // 输出示例: // Inside: pointer value = 0xc000014090, pointed value = 42 // Outside: pointer value = 0xc000014090, pointed value = 42 ← 地址值一致! }

? 补充说明:

  • Go中 new(T) 总是在堆上分配零值 T 并返回 *T(除非被编译器优化到栈上,但语义保证安全);
  • 指针值(如 0xc000014090)是可复制、可返回、可跨作用域传递的“地址数据”,它不依赖于原函数栈帧;
  • 若需调试内存布局,优先使用 %p 格式化动词打印指针值,避免 & 引发的语义混淆。

总结:Go指针的“值”就是内存地址,函数返回指针的本质是返回一个地址数值;而 &variable 永远表示“该变量在当前作用域中的存储位置”,二者层级不同,切勿混用。