[譯] 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。

相關文章
相關標籤/搜索