本节摘要:unsafe 包绕过类型系统直触内存:Pointer 是类型擦除的通用指针,Sizeof、Alignof、Offsetof 三件探针测量内存布局,Add、Slice、String 等受限工具提供受控的底层操作。本节讲清每个工具的语义与实测数值、字符串与切片的零拷贝转换、 uintptr 的 GC 盲区红线,以及"什么场景才配用 unsafe"的门槛。
阅读完本节,你应当能够:
Go 的类型系统与 GC 筑起两道墙:类型墙(*int 不能转 *string)与内存墙(不能手摆对象布局)。但总有些活必须翻墙:与 C 互操作时要传裸指针、极致性能场景要零拷贝、底层运行时与同步原语要直接摸内存布局。unsafe 就是墙上预留的一扇窄门——名字都在警告你:门后没有安全网。
核心角色是 unsafe.Pointer:可持有任何类型地址的通用指针。它构成转换的"桥"——任意具体指针与它互转,再从它转去别的具体指针。数值地址则由 uintptr 承担(一个足够大的无符号整数)。
fmt.Println(unsafe.Sizeof(int(0))) // 8:64位平台 fmt.Println(unsafe.Sizeof("")) // 16:string头=指针+长度两字 fmt.Println(unsafe.Sizeof(true)) // 1 fmt.Println(unsafe.Sizeof([5]int{})) // 40:5乘8 fmt.Println(unsafe.Alignof(int64(0))) // 8
三件探针读的都是"编译期就定死的类型属性",它们本身不危险(只读),是理解布局的教具。Sizeof 量字节数;Alignof 量对齐值(起始地址必须是其倍数);Offsetof 量字段在结构体内的偏移。
对齐带来一个反直觉的事实——字段顺序影响结构体大小:
type Bad struct { A int8 // 1字节,填充7 B int64 // 8字节 C int8 // 1字节,尾部填充7 } // sizeof = 24 type Good struct { B int64 A int8 C int8 } // sizeof = 16,大字段在前省填充
海量实例的结构体把大字段排前面,内存能省三分之一——这类优化只有在探针量过之后才敢做。
Go 1.17 起官方提供了两个替代"uintptr 手工运算"的函数,1.20 又补了字符串对:
零拷贝转换是 unsafe 最著名的正派应用。string 与字节切片互换通常要拷贝,但两者头部结构几乎同构(指针加长度),转头部即可零拷贝:
func bytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) }
风险也随之而来:转换后两边共享同一块内存。string 的不可变承诺被打破——持有转换来的 string,再原地改字节切片,string 内容跟着变,任何依赖字符串不可变的代码(map 的键!)当场踩雷。所以这个技巧的纪律是"转完立刻用、绝不落盘、绝不改源"。

unsafe.Pointer 是"活指针"——GC 认识它、会为它保持对象存活。uintptr 只是整数,GC 完全无视。因此铁律是:Pointer 转到 uintptr 只允许发生在单个表达式内(配合系统调用传地址参数这类场景),绝不许存进变量隔语句使用。存了,期间 GC 搬迁或回收对象,你手里的数值就是指向虚无的悬垂地址,炸法千奇百怪且难复现。Go 1.17 提供 Add 就是为了让"偏移计算"全程留在 Pointer 世界里不出来。
| 场景 | 是否正当 |
|---|---|
| cgo 与 C 库互操作 | 正当,刚需 |
| 零拷贝字符串转换且转完即用 | 可用,守纪律 |
| 字段排序省内存(只用探针测量) | 无害 |
| 运行时、sync 原语等官方内部 | 正当 |
| 业务代码想绕类型系统 | 错误信号 |
判断仪式很简单:写 unsafe 之前问一句"标准库或成熟库有没有已封装的安全版本"——答案几乎总是有。unsafe 的每一行都应该配有注释解释为什么别无选择。
⚠️ 常见坑:以为 unsafe 只影响"这一行"。它破坏的是 GC 与类型系统的全局假设,bug 的爆炸点往往离现场十万八千里。评审见到 unsafe 的正确姿势是要求作者自证必要性。
💡 关键直觉:unsafe 是高压电——电网检修工(运行时、cgo、底层库)摸它是本职;住户想省钱自己接线,烧的是整栋楼。
问答两则。unsafe 代码跨平台安全吗? Sizeof 与偏移在 32/64 位平台数值不同,写死的魔法数换平台就错——探针要用就运行时调,别硬编码。GC 会因为我用了 unsafe 出错吗? 只要遵守"Pointer 保存引用、uintptr 单表达式"的规则,GC 照常正确;违反规则的报应形式是随机崩溃与数据错乱,且几乎无法复现调试。
下一节回到安全区:测试体系。