unsafe.Sizeof 返回数组总字节数而非长度,无法动态获取长度;原生数组长度是编译期常量,应直接用 len();仅切片可通过 reflect.SliceHeader 和 unsafe.Pointer 偏移读取 len 字段,但需确保指针合法且生命周期受控。

unsafe.Sizeof 不能直接拿到数组长度
很多人以为 unsafe.Sizeof 能“看穿”数组,其实它只返回整个数组占用的字节数,不包含长度信息。比如 [5]int64 和 [10]int64 类型不同,unsafe.Sizeof 结果分别是 40 和 80 —— 但你得事先知道类型才能反推长度,运行时无法靠它动态获取。
真正能绕过类型系统读取数组头的,是 reflect.SliceHeader 和 unsafe.Pointer 的组合,但注意:这仅对切片有效,原生数组(如 [10]int)在传参时会退化为值拷贝,地址里不带长度元数据。
- 原生数组长度是编译期常量,用
len(arr)就行,没必要也不该用unsafe - 想从底层指针恢复长度,对象必须是切片(
[]T),不是数组([N]T) -
unsafe.Sizeof对数组类型返回的是总字节大小,不是“长度”,别混淆单位和维度
如何从 *[]T 指针安全还原 len/cap
当你拿到一个指向切片头的 *[]int,可以用 unsafe.Pointer 偏移访问其内部字段。Go 运行时把切片实现为三个机器字长的结构:data、len、cap。在 64 位系统上各占 8 字节,顺序固定。
示例:从 *[]byte 中读取长度
立即学习“go语言免费学习笔记(深入)”;
var s []byte = make([]byte, 3, 5)
ptr := unsafe.Pointer(&s)
lenPtr := (*int)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(reflect.SliceHeader{}.Len)))
fmt.Println(*lenPtr) // 输出 3
- 偏移量必须用
unsafe.Offsetof计算,硬写数字(如 +8)在不同架构或未来版本可能崩 - 必须确保指针真实指向合法切片头,否则是未定义行为(UB),可能 crash 或读到垃圾值
- 该操作绕过 Go 内存模型,GC 不会追踪你手动构造的指针,别把它存进全局变量或长期持有
为什么不能对 []byte 直接做 unsafe.Slice(len) 操作
Go 1.17+ 提供了 unsafe.Slice,但它只接受 unsafe.Pointer 和长度,不负责推导长度 —— 你得自己提供 len 参数。它本质是帮你绕过类型检查构造切片,不是“探测”工具。
常见误解:传个数组首地址进去,指望它自动识别长度。错。它只是把指针 + 长度打包成切片头,不会读内存查边界。
-
unsafe.Slice(p, n)等价于手动构造reflect.SliceHeader{Data: uintptr(p), Len: n, Cap: n} - 若
n超出实际可用内存,后续访问会 panic(“index out of range”)或踩到其他数据 - 没有运行时校验,也不会触发 bounds check elision 优化,性能不比普通切片好
真正需要 unsafe 获取长度的场景极少
绝大多数情况,len() 已经足够快且安全。只有极少数系统编程场景才值得冒险:比如解析 C 传入的裸指针 + 长度对、对接内核 mmap 区域、或实现自定义内存分配器。
但这些场景下,长度通常由外部协议或调用方明确提供,不是靠“黑科技探测”出来的。强行用 unsafe 反推,反而掩盖了设计缺陷。
- CGO 交互中,C 函数一般同时传
void*和size_t len,直接用那个len - 如果只有指针没长度,说明接口设计有问题,该改 C 侧逻辑,而不是在 Go 侧猜
- 所有依赖
unsafe读取切片头的代码,都必须加注释说明:为何不能用len(),以及该指针生命周期是否受 GC 保护
越想绕开类型系统,越要先确认:是不是本来就不该绕。










