go中只有可寻址变量(如局部变量、结构体字段、切片元素)才能用&取地址;字面量、map值、常量等不可寻址;打印地址须用%p,逃逸分析用-go build -gcflags="-m -l"。

用 & 操作符直接取地址,但要注意变量必须可寻址
Go 中只有「可寻址」的变量才能取地址,比如局部变量、结构体字段、切片元素;而字面量、函数返回值、map 的 value、常量这些不可寻址,写 &"hello" 或 &m["k"] 会编译报错:cannot take the address of ...。
实操建议:
- 确认变量是声明过的(如
var x int或x := 42),不是临时计算结果 - 对切片元素取地址是安全的:
&s[0]合法,但&s得到的是切片头结构体的地址,不是底层数组起始地址 - 结构体字段若为导出字段且所在结构体可寻址,也能取:
&v.Field;但&struct{X int}{1}.X报错
用 fmt.Printf("%p", &x) 打印地址,别用 %v 或 %d
%p 是唯一正确定义用于打印指针地址的动词,输出格式如 0xc000010230。用 %v 会输出指针值本身(如 0xc000010230 看似一样,但语义错误),%d 则转成十进制整数,失去可读性和调试意义。
常见错误现象:
立即学习“go语言免费学习笔记(深入)”;
- 写
fmt.Println(&x)→ 输出&{...}或 panic(如果x是不可寻址表达式) - 在日志中混用
%x打印地址 → 结果不带0x前缀,易与普通十六进制数据混淆 - 跨平台时依赖地址数值做逻辑判断 → Go 运行时可能启用 ASLR,每次运行地址都不同,不能用于比较或持久化
调试时想看变量是否逃逸到堆上,用 go build -gcflags="-m -l"
内存地址本身不说明分配位置(栈 or 堆),真正影响性能的是逃逸分析结果。Go 编译器决定变量是否逃逸,而非程序员显式控制。
使用场景和要点:
-
-m显示逃逸决策,-l禁用内联(避免干扰判断),组合使用更清晰 - 输出中出现
mov to heap或escapes to heap表示该变量被分配在堆上 - 即使你拿到了栈上变量的地址(如
&x),只要该地址没逃逸出去(比如没被返回、没存入全局 map),它依然在栈上,函数返回后自动回收 - 注意:goroutine 本地变量取地址后传给另一个 goroutine,大概率触发逃逸 —— 因为生命周期无法被编译器静态确认
用 unsafe.Pointer 和 reflect.Value.UnsafeAddr() 查底层地址,但仅限调试和极少数系统编程
标准方式 &x 拿到的是 Go 类型安全的 *T,而 reflect.Value.UnsafeAddr() 可用于反射对象(如 map value、interface{} 底层值),unsafe.Pointer 则能绕过类型系统做原始地址操作。
为什么谨慎用:
-
reflect.Value.UnsafeAddr()要求值是可寻址且非空接口,对interface{}直接调用会 panic:call of reflect.Value.UnsafeAddr on interface Value - 用
unsafe绕过 GC 管理(比如把栈变量地址存到全局 map)→ 极易造成悬垂指针或内存泄漏 - Go 1.22+ 对
unsafe使用加了更多限制,某些原本合法的转换现在编译失败 - 生产代码里几乎不需要——99% 的调试需求靠
&+%p+-gcflags="-m"已足够
真正容易被忽略的点是:地址值本身没有“栈/堆”标签,同一个地址在不同运行时刻含义可能完全不同;判断内存行为,得看逃逸分析,而不是盯着 0xc000... 猜。










