不能直接互转,必须通过 uintptr 中转;go 类型安全禁止 unsafe.pointer 与普通指针(如 *int)直接赋值,需先转 uintptr 再转回目标指针类型,且转换链须原子完成以避免 gc 回收风险。

unsafe.Pointer 能不能直接和普通指针互转?
不能直接互转,必须通过 uintptr 中转。Go 的类型安全机制禁止 unsafe.Pointer 和任意指针(比如 *int)直接赋值,否则编译报错:cannot convert *int to unsafe.Pointer 或反向错误。
正确写法是先转成 uintptr,再转回目标指针类型:
var x int = 42 p := unsafe.Pointer(&x) // ✅ 取地址得 unsafe.Pointer q := (*int)(unsafe.Pointer(uintptr(p) + unsafe.Offsetof(x))) // ✅ 先转 uintptr 再转回 *int
- 所有指针 →
unsafe.Pointer:只能用&v或unsafe.Pointer(&v)(显式转换) -
unsafe.Pointer→ 其他指针:必须经过uintptr中转,且仅允许一次转换(即(*T)(unsafe.Pointer(uintptr(p)))合法,(*T)(unsafe.Pointer(uintptr(unsafe.Pointer(&x))))多余但不报错,但易读性差) - 直接写
(*int)(p)会编译失败 —— 这是 Go 故意设的屏障,防止误操作
什么时候必须用 unsafe.Pointer?
典型场景是绕过 Go 类型系统做底层内存操作,比如:
- 实现自定义 slice 构造(如从固定数组头构造 slice,不触发底层数组拷贝)
- 在
reflect包中获取结构体字段地址(Field(0).UnsafeAddr()返回uintptr,需转unsafe.Pointer才能进一步转指针) - 与 C 函数交互时传递内存块(如
C.malloc返回unsafe.Pointer,需转为 Go 指针才能读写) - 高性能序列化/反序列化中跳过反射开销(如 fastjson、gogoprotobuf 内部大量使用)
注意:只要标准库或第三方包已有安全封装(如 reflect.SliceHeader 配合 unsafe.Slice(Go 1.20+)),优先用它们,而不是手写 unsafe.Pointer 转换逻辑。
常见崩溃原因:uintptr 被 GC 误回收
这是最隐蔽也最致命的问题。当你把指针转成 uintptr 后,Go 的垃圾回收器就“看不见”这个内存地址了 —— 它只认 unsafe.Pointer 或普通指针变量。
下面这段代码极大概率崩溃:
func bad() *int {
x := new(int)
p := uintptr(unsafe.Pointer(x))
runtime.GC() // 可能触发回收 x 所在内存
return (*int)(unsafe.Pointer(p)) // ❌ p 已失效,解引用 panic
}
- 只要中间出现独立的
uintptr变量(没立刻转回指针),就存在被 GC 回收的风险 - 正确做法是:转换链必须原子完成,不落地为变量,例如
(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + offset)) - 如果必须存中间值,用
unsafe.Pointer存,而不是uintptr;GC 会跟踪unsafe.Pointer的生命周期
Go 1.20+ 替代方案:unsafe.Slice 和 unsafe.String
很多过去靠 unsafe.Pointer 手动构造 slice 的场景,现在可以直接用标准库函数,更安全、更清晰:
data := []byte("hello")
s := unsafe.Slice((*int)(unsafe.Pointer(&data[0])), len(data)/unsafe.Sizeof(int(0))) // 旧写法,易错
s := unsafe.Slice((*int)(unsafe.Pointer(unsafe.SliceData(data))), len(data)/unsafe.Sizeof(int(0))) // 更稳妥,但还是绕弯
// ✅ 推荐:用 unsafe.SliceData + unsafe.Slice 组合(Go 1.20+)
s := unsafe.Slice((*int)(unsafe.Pointer(unsafe.SliceData(data))), len(data)/unsafe.Sizeof(int(0)))
-
unsafe.SliceData(slice)直接返回底层数据首地址的unsafe.Pointer,比&slice[0]更可靠(对零长度 slice 也有效) -
unsafe.Slice(*T, len)安全构造 slice,内部已处理 GC 可达性问题 - 这些函数不降低安全性,但大幅减少手写指针运算出错的概率 —— 它们才是“突破类型系统限制”的现代写法
真正难的不是怎么写 unsafe.Pointer,而是判断什么时候不该用它。多数业务代码根本不需要碰它;一旦用了,就得全程盯住指针生命周期、内存对齐、GC 可达性这三件事 —— 少一个环节,运行时就可能静默崩掉。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











