
Go 里不能做指针算术,unsafe.Pointer 不是 C 的 void*
Go 明确禁止对普通指针(如 *int)做加减运算,比如 p + 1 直接报错:invalid operation: p + 1 (mismatched types *int and int)。这不是语法限制,而是类型安全设计的硬性边界。unsafe.Pointer 确实能绕过类型检查,但它本身不支持算术——必须先转成 uintptr 才能加减,再转回 unsafe.Pointer。
常见错误是直接写 unsafe.Pointer(&x) + 4,这会编译失败。正确路径只有一条:uintptr(unsafe.Pointer(&x)) + 4,然后用 unsafe.Pointer() 包一层。
- 加减操作必须在
uintptr上进行,unsafe.Pointer本身不可运算 - 所有转换必须显式,Go 不允许隐式类型穿透
- 转换链越长(比如
*T → unsafe.Pointer → uintptr → unsafe.Pointer → *U),越容易因 GC 或编译器优化出问题
用 unsafe.Offsetof 比硬编码偏移更安全
想访问 struct 字段的内存地址?别手算字节偏移。字段对齐、填充、平台差异会让 unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + 8) 在 32 位机器或不同 Go 版本上突然失效。
unsafe.Offsetof(s.field) 返回的是从 struct 起始到该字段首字节的偏移量,由编译器保证正确。它返回 uintptr,可直接用于地址计算。
立即学习“go语言免费学习笔记(深入)”;
- 永远优先用
unsafe.Offsetof,而不是靠unsafe.Sizeof自己累加 -
unsafe.Offsetof只接受字段选择器(s.f),不能传表达式或临时值 - 字段必须是导出的(首字母大写)才能被
unsafe.Offsetof接受,否则编译报错:cannot refer to unexported field
reflect.SliceHeader 和 reflect.StringHeader 是危险捷径
想把 []byte 快速转成 string 而不拷贝?很多人直接改 reflect.StringHeader 的 Data 和 Len 字段,再用 unsafe.Pointer 转成 string。这确实快,但有两个致命前提:
- 源
[]byte的底层数组不能被回收——这意味着不能传入局部 slice(比如函数参数里新切出来的),否则 string 可能指向已释放内存 - Go 1.20+ 开始,
string的底层数据默认不可写,但更关键的是:GC 不跟踪通过unsafe构造的 string 引用,可能提前回收底层数组 - 这种转换在 cgo 场景下尤其脆弱,C 函数修改内存后,Go runtime 可能完全不知情
为什么 unsafe.Slice(Go 1.17+)比手动构造 []T 更可靠
手动用 unsafe.Pointer + reflect.SliceHeader 构造 slice,需要自己填 Data、Len、Cap,稍有不慎就导致 panic 或越界读写。Go 1.17 引入的 unsafe.Slice 把这个过程封装成一个函数调用,它做了三件事:校验指针非 nil、检查长度是否溢出、确保结果 slice 符合 runtime 内存模型。
- 用
unsafe.Slice((*T)(ptr), len)替代手拼reflect.SliceHeader,代码更短,语义更清晰 -
unsafe.Slice不接受nil指针,也不接受负长度,运行时会 panic,反而帮你提前暴露问题 - 它返回的 slice 会被 GC 正常追踪——只要原始指针所指内存本身是活的(比如来自
make或全局变量)
真正难的从来不是怎么突破类型系统,而是让突破后的内存生命周期和 Go 的 GC 节奏对得上。哪怕一行 unsafe,也得想清楚:这块内存谁分配、谁释放、谁持有引用、会不会被编译器优化掉。










