%p只打印指针值地址,非对象地址;必须传指针或uintptr类型,不能直接传非指针变量;复合类型&v取的是头地址,非底层数据地址;查底层数组需unsafe,但map等类型不支持;地址不可移植、不可用于内存泄漏分析。

Go 里用 %p 打印指针地址,不是对象地址
Go 没有“对象内存地址”这个概念——它没有 C 那种可直接取任意变量地址的裸指针语义(除非用 unsafe),%p 只能打印指针类型的值,也就是某个变量的地址。你写 fmt.Printf("%p", &x),输出的是 x 在栈或堆上的地址;但写 fmt.Printf("%p", x)(x 是非指针类型)会编译报错:cannot use x (type int) as type interface {} in argument to fmt.Printf。
-
%p的参数必须是uintptr或任意指针类型(如*int、*string),底层被当作uintptr处理 - 对结构体、切片、map 等复合类型,
&v拿到的是该值头(header)的地址,不是底层数据块地址(比如切片底层数组地址得用unsafe) - 不要试图用
%p判断两个变量是否“同一份数据”——比如两个切片内容相同但底层数组不同,&s1和&s2地址肯定不同,但这和数据共享无关
想看底层数据地址?比如切片底层数组起始位置
标准库不提供,必须用 unsafe。例如获取切片 s 底层数组首地址:
import "unsafe"
s := []int{1, 2, 3}
if len(s) > 0 {
ptr := unsafe.Pointer(&s[0])
fmt.Printf("%p\n", ptr) // 输出数组第一个元素地址
}
注意:&s[0] 要求 len(s) > 0,否则 panic;空切片不能这么取。另外,unsafe.Pointer 传给 fmt.Printf("%p", ...) 是合法的,因为 fmt 内部把它当 uintptr 解释。
- map、channel、func 类型无法通过
unsafe安全取底层地址——它们的内存布局不公开,也不保证稳定 - 使用
unsafe后代码失去内存安全保证,GC 可能无法追踪该指针,别长期持有或跨 goroutine 传递 - 如果只是调试用,建议只在本地开发环境启用,CI 或生产构建中禁用相关代码
%p 输出格式和跨平台差异
%p 默认输出十六进制地址,前面带 0x,比如 0xc000014080。它不保证位宽一致:在 32 位系统上可能是 0x12345678(8 位),64 位上是 0xc000014080(12 位以上)。别依赖字符串长度做解析。
立即学习“go语言免费学习笔记(深入)”;
- 地址值本身没有可移植含义——同一程序两次运行,甚至同一运行中两次分配,地址都不同
- 不要把
%p输出存日志后拿去“比对内存泄漏”,Go 的 GC 和分配器行为复杂,地址复用很常见 - 若需唯一标识一个值用于调试,考虑用
fmt.Sprintf("%p", &x)+ 变量名拼接,而不是单靠地址
常见错误:把接口值、反射值当地址打
写 fmt.Printf("%p", interface{}(x)) 或 fmt.Printf("%p", reflect.ValueOf(x).Interface()),结果总是类似 0x0 或随机小地址——因为接口值和反射值本身是结构体,%p 打的是那个结构体的地址,不是原始数据地址,而且可能触发隐式拷贝或包装。
- 对
interface{}类型变量v,&v是接口头地址,不是里面装的值的地址 - 想看接口内具体值的地址?先类型断言成具体指针,再打:
if p, ok := v.(*int); ok { fmt.Printf("%p", p) } - 用
reflect.Value.Addr()前必须确认CanAddr()返回 true,否则 panic;且它返回的是反射封装的地址,仍需转unsafe.Pointer才能用%p
%p 以为能定位问题——多数时候你真正该看的是逃逸分析结果、pprof 堆分配、或者用 runtime.ReadMemStats 看整体趋势。










