[译] Go: 什么是 Unsafe 包?

ℹ️本文基于 Go 1.12。git

看到 Unsafe 这个名称,咱们应该尽可能避免使用它。想要知道使用 Unsafe 包可能产生不安全的缘由,咱们首先来看看官方文档的描述:github

unsafe 包含有违背 Go 类型安全的操做。
导入 unsafe 包可能会使程序不可移植,而且不受 Go 1 兼容性指南的保护。golang

所以,该名称被用做提示 unsafe 包可能带来 Go 类型的不安全性。如今咱们来深刻探讨一下文档中提到的两点。安全

类型安全

在 Go 中,每一个变量都有一个类型,能够在分配给另外一个变量以前转换为另外一个类型。在此转换期间,Go 会对此数据执行转换,以适应请求的类型。来看下面这个例子:函数

var i int8 = -1 // -1 二进制表示: 11111111
var j = int16(i) // -1 二进制表示: 11111111 11111111
println(i, j) // -1 -1
复制代码

unsafe 包让咱们能够直接访问此变量的内存,并将原始二进制值存储在此地址中。在绕过类型约束时,咱们能够根据须要使用它:post

var k uint8 = *(*uint8)(unsafe.Pointer(&i))
println(k) // 255 is the uint8 value for the binary 11111111
复制代码

如今,原始值被解释为 uint8,而没有使用先前声明的类型(int8)。若是你有兴趣深刻了解此主题,我建议你阅读我关于使用 Go 进行 Cast 和 Conversion的文章。ui

Go 1 兼容性指南

Go 1 的指南清楚地解释了若是你的代码使用了 unsafe包, 在更改实现以后可能会破坏你的代码:spa

导入unsafe软件包可能取决于 Go 实现的内部属性。 咱们保留对可能致使程序崩溃的实现进行修改的权利。翻译

咱们应该记住,在 Go 1 中,内部实现可能会发生变化,咱们可能会遇到像这个Github issue中相似的问题,两个版本之间的行为略有变化。可是,Go 标准库在许多地方也使用了 unsafe 包。设计

在 Go 的 reflect 包中使用

reflection 包是最经常使用的包之一。反射基于空接口包含的内部数据。要读取数据,Go 只是将咱们的变量转换为空接口,并经过将与空接口的内部表示匹配的结构和指针地址处的内存映射来读取它们:

func ValueOf(i interface{}) Value {
   [...]
   return unpackEface(i)
}
// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
   e := (*emptyInterface)(unsafe.Pointer(&i))
   [...]
}
复制代码

变量e如今包含有关值的全部信息,例如类型或是否已导出值。反射还使用unsafe包经过直接更新内存中的值来修改反射变量的值,如前所述。

在 Go 的 sync 包中使用

unsafe 包的另外一个有趣用法是在sync包中。若是你不熟悉 sync 包,我建议你阅读个人sync.Pool 的设计的一篇文章。

这些池经过一段内存在全部 goroutine/processors 之间共享,全部 goroutine 均可以经过unsafe包访问:

func indexLocal(l unsafe.Pointer, i int) *poolLocal {
   lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
   return (*poolLocal)(lp)
}
复制代码

变量 l 是内存段,i 是处理器编号。函数 indexLocal 只读取此内存段 - 包含 X(处理器数量)poolLocal 结构 - 具备与其读取的索引相关的偏移量。存储指向完整内存段的指针是实现共享池的一种很是轻松的方法。

在 Go 的 runtime 包中使用

Go 还在 runtime 包中使用了 unsafe 包,由于它必须处理内存操做,如堆栈分配或释放堆栈内存。堆栈在其结构中由两个边界表示:

type stack struct {
   lo uintptr
   hi uintptr
}
复制代码

那么 unsafe 包将有助于进行操做:

func stackfree(stk stack) {
   [...]
   v := unsafe.Pointer(stk.lo)
   n := stk.hi - stk.lo
   // 而后基于指向堆栈的指针释放内存
   [...]
}
复制代码

若是你想进一步了解堆栈,我建议你阅读我关于堆栈大小及其管理的文章

此外,在某些状况下,咱们也能够在咱们的应用程序中使用此包,例如结构之间的转换。

unsafe 包对开发人员的用处

unsafe 包的一个很好的用法是使用相同的底层数据转换两个不一样的结构,这是转换器没法实现的:

type A struct {
   A int8
   B string
   C float32

}

type B struct {
   D int8
   E string
   F float32

}

func main() {
   a := A{A: 1, B: `foo`, C: 1.23}
   //b := B(a) 不能转换 a (type A) 到 type B
   b := *(*B)(unsafe.Pointer(&a))

   println(b.D, b.E, b.F) // 1 foo 1.23
}
复制代码

源码:play.golang.org/p/sjeO9v0T_…

unsafe 包中另外一个不错的用法是golang-sizeof.tips,它能够帮助你理解结构内存对齐的大小。

总之,该软件包很是有趣且功能强大,可是应该谨慎使用。此外,若是你对unsafe包的未来的修改有建议,你能够在Github for Go 2中提 Issue。

相关文章
相关标签/搜索