内容简介::information_source:本文基于 Go 1.12。看到 Unsafe 这个名称,我们应该尽量避免使用它。想要知道使用 Unsafe 包可能产生不安全的原因,我们首先来看看官方文档的描述:unsafe 包含有违背 Go 类型安全的操作。
- 原文地址: medium.com/@blanchon.v…
- 原文作者: Vincent Blanchon
- 译文地址: github.com/watermelo/d…
- 译者:咔叽咔叽
- 译者水平有限,如有翻译或理解谬误,烦请帮忙指出
:information_source:本文基于 Go 1.12。
看到 Unsafe 这个名称,我们应该尽量避免使用它。想要知道使用 Unsafe 包可能产生不安全的原因,我们首先来看看官方文档的描述:
unsafe 包含有违背 Go 类型安全的操作。
导入 unsafe 包可能会使程序不可移植,并且不受 Go 1 兼容性指南的保护。
因此,该名称被用作提示 unsafe 包可能带来 Go 类型的不安全性。现在我们来深入探讨一下文档中提到的两点。
类型安全
在 Go 中,每个变量都有一个类型,可以在分配给另一个变量之前转换为另一个类型。在此转换期间,Go 会对此数据执行转换,以适应请求的类型。来看下面这个例子:
var i int8 = -1 // -1 二进制表示: 11111111 var j = int16(i) // -1 二进制表示: 11111111 11111111 println(i, j) // -1 -1 复制代码
unsafe
包让我们可以直接访问此变量的内存,并将原始二进制值存储在此地址中。在绕过类型约束时,我们可以根据需要使用它:
var k uint8 = *(*uint8)(unsafe.Pointer(&i)) println(k) // 255 is the uint8 value for the binary 11111111 复制代码
现在,原始值被解释为 uint8,而没有使用先前声明的类型(int8)。如果你有兴趣深入了解此主题,我建议你阅读我关于 使用 Go 进行 Cast 和 Conversion 的文章。
Go 1 兼容性指南
Go 1 的指南
清楚地解释了如果你的代码使用了 unsafe
包, 在更改实现之后可能会破坏你的代码:
导入 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。
以上所述就是小编给大家介绍的《[译] Go: 什么是 Unsafe 包?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。