如何将Golang中打印指针地址的操作,巧妙地转化为一个长尾?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1108个文字,预计阅读时间需要5分钟。
在Go语言中,不能直接使用`fmt.Println`来打印指针地址得到十六进制格式。默认情况下,打印指针会输出类似于十六进制的地址,但它不是严格的十六进制格式。
直接使用`fmt.Println(&var)`会输出变量的地址,通常格式类似于`0xXXXX`,其中`XXXX`是十六进制表示的地址。如果要获取严格的十六进制格式输出,需要使用其他方法,比如:
注意:%p 要求参数是任意类型的指针(*T),传入非指针会 panic:
var x int = 42 fmt.Printf("%p\n", &x) // ✅ 正确:传 &x,即 *int fmt.Printf("%p\n", x) // ❌ panic: fmt: cannot print value of type int
常见误操作:
- 对 nil 指针用
%p会输出0x0,不是空字符串也不是 panic - 对切片、map、channel 等引用类型变量本身用
&v得到的不是底层数据地址,而是该 header 结构体的地址(比如&[]int{1,2}打印的是 slice header 在栈上的地址,不是底层数组地址)
想看底层数组地址?得用 unsafe 配合 reflect
普通指针地址(如 &x)好办,但像 []byte 或 []int 这类切片,其数据实际存在底层数组里,而切片变量本身只是 header(含 ptr、len、cap)。要拿到真实数据起始地址,得绕过类型系统:
立即学习“go语言免费学习笔记(深入)”;
安全做法(推荐):
data := []int{1, 2, 3} if len(data) > 0 { ptr := unsafe.Pointer(&data[0]) fmt.Printf("底层数组首地址: %p\n", ptr) }
不安全但有时必要的情况(比如调试 runtime 行为):
- 对空切片(
len==0),&data[0]会 panic,需先判空 -
unsafe.Pointer不能直接传给fmt.Printf("%p", ...),必须转成*byte或其他具体指针类型才能被%p接受 - 启用
go run -gcflags="-l"可能影响变量是否真的分配在堆上,导致地址看起来“不稳定”,这不是 bug,是编译器优化结果
fmt.Sprintf("%p", ...) 返回的是字符串,不是地址值
有人想把地址存下来做日志关联或比对,于是写 s := fmt.Sprintf("%p", &x) ——这没问题,但要注意返回的是形如 "0xc000010230" 的字符串,不是可运算的整数。如果后续需要做地址算术(比如偏移计算),必须用 uintptr:
addr := uintptr(unsafe.Pointer(&x)) offsetAddr := addr + 8 // 向后偏移 8 字节 fmt.Printf("偏移后地址: %p\n", (*byte)(unsafe.Pointer(uintptr(offsetAddr))))
关键点:
-
uintptr是整数类型,可参与运算;unsafe.Pointer不是,不能加减 - 从
uintptr转回unsafe.Pointer必须再套一层(*T)(...)强制转换,否则fmt.Printf("%p", someUintptr)会报错 - GC 可能移动堆对象,所以基于地址的长期缓存或跨 goroutine 传递要格外小心
调试时更实用的替代方案:用 runtime/debug.PrintStack() 或 pprof
单纯为了定位问题,硬抠内存地址往往事倍功半。比如想确认两个变量是否指向同一块内存,用 == 比较指针更可靠:
a := &x b := &x fmt.Println(a == b) // true
而真正需要地址信息的场景其实有限:
- 排查 cgo 传参时 C 端收到的地址是否异常
- 分析逃逸分析结果(
go build -gcflags="-m"输出里的escapes to heap) - 配合
pprof查内存分布:go tool pprof mem.pprof→top或web
这时候地址只是辅助线索,重点还是结合上下文看数据生命周期和所有权。
本文共计1108个文字,预计阅读时间需要5分钟。
在Go语言中,不能直接使用`fmt.Println`来打印指针地址得到十六进制格式。默认情况下,打印指针会输出类似于十六进制的地址,但它不是严格的十六进制格式。
直接使用`fmt.Println(&var)`会输出变量的地址,通常格式类似于`0xXXXX`,其中`XXXX`是十六进制表示的地址。如果要获取严格的十六进制格式输出,需要使用其他方法,比如:
注意:%p 要求参数是任意类型的指针(*T),传入非指针会 panic:
var x int = 42 fmt.Printf("%p\n", &x) // ✅ 正确:传 &x,即 *int fmt.Printf("%p\n", x) // ❌ panic: fmt: cannot print value of type int
常见误操作:
- 对 nil 指针用
%p会输出0x0,不是空字符串也不是 panic - 对切片、map、channel 等引用类型变量本身用
&v得到的不是底层数据地址,而是该 header 结构体的地址(比如&[]int{1,2}打印的是 slice header 在栈上的地址,不是底层数组地址)
想看底层数组地址?得用 unsafe 配合 reflect
普通指针地址(如 &x)好办,但像 []byte 或 []int 这类切片,其数据实际存在底层数组里,而切片变量本身只是 header(含 ptr、len、cap)。要拿到真实数据起始地址,得绕过类型系统:
立即学习“go语言免费学习笔记(深入)”;
安全做法(推荐):
data := []int{1, 2, 3} if len(data) > 0 { ptr := unsafe.Pointer(&data[0]) fmt.Printf("底层数组首地址: %p\n", ptr) }
不安全但有时必要的情况(比如调试 runtime 行为):
- 对空切片(
len==0),&data[0]会 panic,需先判空 -
unsafe.Pointer不能直接传给fmt.Printf("%p", ...),必须转成*byte或其他具体指针类型才能被%p接受 - 启用
go run -gcflags="-l"可能影响变量是否真的分配在堆上,导致地址看起来“不稳定”,这不是 bug,是编译器优化结果
fmt.Sprintf("%p", ...) 返回的是字符串,不是地址值
有人想把地址存下来做日志关联或比对,于是写 s := fmt.Sprintf("%p", &x) ——这没问题,但要注意返回的是形如 "0xc000010230" 的字符串,不是可运算的整数。如果后续需要做地址算术(比如偏移计算),必须用 uintptr:
addr := uintptr(unsafe.Pointer(&x)) offsetAddr := addr + 8 // 向后偏移 8 字节 fmt.Printf("偏移后地址: %p\n", (*byte)(unsafe.Pointer(uintptr(offsetAddr))))
关键点:
-
uintptr是整数类型,可参与运算;unsafe.Pointer不是,不能加减 - 从
uintptr转回unsafe.Pointer必须再套一层(*T)(...)强制转换,否则fmt.Printf("%p", someUintptr)会报错 - GC 可能移动堆对象,所以基于地址的长期缓存或跨 goroutine 传递要格外小心
调试时更实用的替代方案:用 runtime/debug.PrintStack() 或 pprof
单纯为了定位问题,硬抠内存地址往往事倍功半。比如想确认两个变量是否指向同一块内存,用 == 比较指针更可靠:
a := &x b := &x fmt.Println(a == b) // true
而真正需要地址信息的场景其实有限:
- 排查 cgo 传参时 C 端收到的地址是否异常
- 分析逃逸分析结果(
go build -gcflags="-m"输出里的escapes to heap) - 配合
pprof查内存分布:go tool pprof mem.pprof→top或web
这时候地址只是辅助线索,重点还是结合上下文看数据生命周期和所有权。

