Golang的unsafe包是一個很特殊的包。 爲何這樣說呢? 本文將詳細解釋。程序員
unsafe包的文檔是這麼說的:golang
導入unsafe的軟件包可能不可移植,而且不受Go 1兼容性指南的保護。
Go 1 兼容性指南這麼說:express
導入unsafe軟件包可能取決於Go實現的內部屬性。 咱們保留對可能致使程序崩潰的實現進行更改的權利。
固然包名稱暗示unsafe包是不安全的。 但這個包有多危險呢? 讓咱們先看看unsafe包的做用。數組
直到如今(Go1.7),unsafe包含如下資源:安全
三個函數:app
和一種類型:函數
這裏,ArbitraryType不是一個真正的類型,它只是一個佔位符。佈局
與Golang中的大多數函數不一樣,上述三個函數的調用將始終在編譯時求值,而不是運行時。 這意味着它們的返回結果能夠分配給常量。ui
(BTW,unsafe包中的函數中非惟一調用將在編譯時求值。當傳遞給len和cap的參數是一個數組值時,內置函數和cap函數的調用也能夠在編譯時被求值。)this
除了這三個函數和一個類型外,指針在unsafe包也爲編譯器服務。
出於安全緣由,Golang不容許如下之間的直接轉換:
兩個不一樣指針類型的值,例如 int64和 float64。
可是藉助unsafe.Pointer,咱們能夠打破Go類型和內存安全性,並使上面的轉換成爲可能。這怎麼可能發生?讓咱們閱讀unsafe包文檔中列出的規則:
這些規則與Go規範一致:
底層類型uintptr的任何指針或值均可以轉換爲指針類型,反之亦然。
規則代表unsafe.Pointer相似於c語言中的void 。固然,void 在C語言裏是危險的!
在上述規則下,對於兩種不一樣類型T1和T2,可使 T1值與unsafe.Pointer值一致,而後將unsafe.Pointer值轉換爲 T2值(或uintptr值)。經過這種方式能夠繞過Go類型系統和內存安全性。固然,濫用這種方式是很危險的。
舉個例子:
package main import ( "fmt" "unsafe" ) func main() { var n int64 = 5 var pn = &n var pf = (*float64)(unsafe.Pointer(pn)) // now, pn and pf are pointing at the same memory address fmt.Println(*pf) // 2.5e-323 *pf = 3.14159 fmt.Println(n) // 4614256650576692846 }
在這個例子中的轉換多是無心義的,但它是安全和合法的(爲何它是安全的?)。
所以,資源在unsafe包中的做用是爲Go編譯器服務,unsafe.Pointer類型的做用是繞過Go類型系統和內存安全。
這裏有一些關於unsafe.Pointer和uintptr的事實:
因爲uintptr是一個整數類型,uintptr值能夠進行算術運算。 因此經過使用uintptr和unsafe.Pointer,咱們能夠繞過限制,* T值不能在Golang中計算偏移量:
package main import ( "fmt" "unsafe" ) func main() { a := [4]int{0, 1, 2, 3} p1 := unsafe.Pointer(&a[1]) p3 := unsafe.Pointer(uintptr(p1) + 2 * unsafe.Sizeof(a[0])) *(*int)(p3) = 6 fmt.Println("a =", a) // a = [0 1 2 6] // ... type Person struct { name string age int gender bool } who := Person{"John", 30, true} pp := unsafe.Pointer(&who) pname := (*string)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.name))) page := (*int)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.age))) pgender := (*bool)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.gender))) *pname = "Alice" *page = 28 *pgender = false fmt.Println(who) // {Alice 28 false} }
關於unsafe包,Ian,Go團隊的核心成員之一,已經確認:
在unsafe包中的函數的簽名將不會在之後的Go版本中更改,
因此,unsafe包中的三個函數看起來不危險。 go team leader甚至想把它們放在別的地方。 unsafe包中這幾個函數惟一不安全的是它們調用結果可能在後來的版本中返回不一樣的值。 很難說這種不安全是一種危險。
看起來全部的unsafe包的危險都與使用unsafe.Pointer有關。 unsafe包docs列出了一些使用unsafe.Pointer合法或非法的狀況。 這裏只列出部分非法使用案例:
package main import ( "fmt" "unsafe" ) // case A: conversions between unsafe.Pointer and uintptr // don't appear in the same expression func illegalUseA() { fmt.Println("===================== illegalUseA") pa := new([4]int) // split the legal use // p1 := unsafe.Pointer(uintptr(unsafe.Pointer(pa)) + unsafe.Sizeof(pa[0])) // into two expressions (illegal use): ptr := uintptr(unsafe.Pointer(pa)) p1 := unsafe.Pointer(ptr + unsafe.Sizeof(pa[0])) // "go vet" will make a warning for the above line: // possible misuse of unsafe.Pointer // the unsafe package docs, https://golang.org/pkg/unsafe/#Pointer, // thinks above splitting is illegal. // but the current Go compiler and runtime (1.7.3) can't detect // this illegal use. // however, to make your program run well for later Go versions, // it is best to comply with the unsafe package docs. *(*int)(p1) = 123 fmt.Println("*(*int)(p1) :", *(*int)(p1)) // } // case B: pointers are pointing at unknown addresses func illegalUseB() { fmt.Println("===================== illegalUseB") a := [4]int{0, 1, 2, 3} p := unsafe.Pointer(&a) p = unsafe.Pointer(uintptr(p) + uintptr(len(a)) * unsafe.Sizeof(a[0])) // now p is pointing at the end of the memory occupied by value a. // up to now, although p is invalid, it is no problem. // but it is illegal if we modify the value pointed by p *(*int)(p) = 123 fmt.Println("*(*int)(p) :", *(*int)(p)) // 123 or not 123 // the current Go compiler/runtime (1.7.3) and "go vet" // will not detect the illegal use here. // however, the current Go runtime (1.7.3) will // detect the illegal use and panic for the below code. p = unsafe.Pointer(&a) for i := 0; i <= len(a); i++ { *(*int)(p) = 123 // Go runtime (1.7.3) never panic here in the tests fmt.Println(i, ":", *(*int)(p)) // panic at the above line for the last iteration, when i==4. // runtime error: invalid memory address or nil pointer dereference p = unsafe.Pointer(uintptr(p) + unsafe.Sizeof(a[0])) } } func main() { illegalUseA() illegalUseB() }
編譯器很難檢測Go程序中非法的unsafe.Pointer使用。 運行「go vet」能夠幫助找到一些潛在的錯誤,但不是全部的都能找到。 一樣是Go運行時,也不能檢測全部的非法使用。 非法unsafe.Pointer使用可能會使程序崩潰或表現得怪異(有時是正常的,有時是異常的)。 這就是爲何使用不安全的包是危險的。
對於將 T1轉換爲unsafe.Pointer,而後轉換爲 T2,unsafe包docs說:
若是T2比T1大,而且二者共享等效內存佈局,則該轉換容許將一種類型的數據從新解釋爲另外一類型的數據。
這種「等效內存佈局」的定義是有一些模糊的。 看起來go團隊故意如此。 這使得使用unsafe包更危險。
因爲Go團隊不肯意在這裏作出準確的定義,本文也不嘗試這樣作。 這裏,列出了已確認的合法用例的一小部分,
在這個例子裏,咱們用int做爲T:
type MyInt int
在Golang中,[] int和[] MyInt是兩種不一樣的類型,它們的底層類型是自身。 所以,[] int的值不能轉換爲[] MyInt,反之亦然。 可是在unsafe.Pointer的幫助下,轉換是可能的:
package main import ( "fmt" "unsafe" ) func main() { type MyInt int a := []MyInt{0, 1, 2} // b := ([]int)(a) // error: cannot convert a (type []MyInt) to type []int b := *(*[]int)(unsafe.Pointer(&a)) b[0]= 3 fmt.Println("a =", a) // a = [3 1 2] fmt.Println("b =", b) // b = [3 1 2] a[2] = 9 fmt.Println("a =", a) // a = [3 1 9] fmt.Println("b =", b) // b = [3 1 9] }
sync / atomic包中的如下函數的大多數參數和結果類型都是unsafe.Pointer或*unsafe.Pointer:
要使用這些功能,必須導入unsafe包。 注意: unsafe.Pointer是通常類型,所以 unsafe.Pointer的值能夠轉換爲unsafe.Pointer,反之亦然。
package main import ( "fmt" "log" "time" "unsafe" "sync/atomic" "sync" "math/rand" ) var data *string // get data atomically func Data() string { p := (*string)(atomic.LoadPointer( (*unsafe.Pointer)(unsafe.Pointer(&data)), )) if p == nil { return "" } else { return *p } } // set data atomically func SetData(d string) { atomic.StorePointer( (*unsafe.Pointer)(unsafe.Pointer(&data)), unsafe.Pointer(&d), ) } func main() { var wg sync.WaitGroup wg.Add(200) for range [100]struct{}{} { go func() { time.Sleep(time.Second * time.Duration(rand.Intn(1000)) / 1000) log.Println(Data()) wg.Done() }() } for i := range [100]struct{}{} { go func(i int) { time.Sleep(time.Second * time.Duration(rand.Intn(1000)) / 1000) s := fmt.Sprint("#", i) log.Println("====", s) SetData(s) wg.Done() }(i) } wg.Wait() fmt.Println("final data = ", *data) }