Go Unsafe包
翻译自
https://medium.com/a-journey-with-go/go-what-is-the-unsafe-package-d2443da36350
正文
包的名称指引着我们的使用。了解此包可能不安全的原因,让我们首先查看如下文档
1 | unsafe包包含可以绕过Go程序类型安全的操作 |
因此,该名称被用作对为 Go 提供类型的安全性的反面。 现在让我们深入研究文档中提到的这两点。
类型安全
在Go中,每个变量都拥有一个类型,在赋值给另外一个变量之前,可以转换为另外的类型。在此转换期间,Go 执行此数据的转换以适应所请求的类型。 这是一个例子
1 | package unsafe_test |
输出
1 | -1 -1 |
unsafe包让我们直接操作这个变量的内存地址,直接获取里面的二进制值。在绕过类型约束时,我们可以随意使用它。
1 | var k uint8 = *(*uint8)(unsafe.Pointer(&i)) |
Go1 兼容性指南
unsafe包可能会依赖于Go的底层实现。Go可能会做出破坏性的改动。
Go 反射包中使用Unsafe
reflect包是使用最多的一个包。反射基于空interface包含的数据。为了读取数据,Go只是将我们的变量转化为一个空接口,并通过映射一个结构体来读取它们,该结构体匹配空接口的内部表示与指针地址处的内存。
1 | func ValueOf(i interface{}) Value { |
变量 e 现在包含有关该值的所有信息,例如类型或该值是否已导出。 反射还使用 unsafe 包通过直接在内存中更新值来修改反射变量的值。
Go Sync包使用Unsafe
sync.Pool 这些池通过一段go协程都能访问的内存片段来共享数据。这访问模式和C语言数组的访问方式很像
1 | func indexLocal(l unsafe.Pointer, i int) *poolLocal { |
l是内存片段,i是数字。函数 indexLocal 只是读取这个内存段——它包含 X个poolLocal 结构——与它读取的索引相关的偏移量。 存储指向完整内存段的单个指针是实现共享池的一种非常轻量的方式。
在Go runtime包中的使用
Go 在runtime包也大量使用 unsafe 包,因为它必须处理内存操作,如栈分配或释放栈内存。 栈由其结构中的两个边界表示:
1 | type stack struct { |
unsafe包可以完成这个操作
1 | func stackfree(stk stack) { |
开发平时使用
unsafe包的一个很好的用法是转换具有相同底层数据结构的不同结构体,这是转换器无法实现的
1 | package unsafe |