關於go中的unsafe包

Unsafe code

Unsafe code是一種繞過go類型安全和內存安全檢查的Go代碼。大多數狀況,unsafe code是和指針相關的。可是要記住使用unsafe code有可能會損害你的程序,因此,若是你不徹底肯定是否須要用到unsafe code就不要使用它。小程序

如下面的unsafe.go爲例,看一下unsafe code的使用數組

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var value int64 = 5
	var p1 = &value
	var p2 = (*int32)(unsafe.Pointer(p1))
複製代碼

這裏使用了unsafe.Pointer()方法,這個方法能讓你創造一個int32p2指針去指向一個int64value變量,而這個變量是使用p1指針去訪問的,注意這種作法是有風險的。安全

任何go指針均可以轉化爲unsafe.Pointer指針。bash

unsafe.Pointer類型的指針能夠覆蓋掉go的系統類型。這毫無疑問很快,可是若是不當心或者不正確使用的話就會很危險,它給了開發者更多選擇去掌控數據。

unsafe.go後面部分以下架構

fmt.Println("*p1: ", *p1)
	fmt.Println("*p2: ", *p2)
	*p1 = 5434123412312431212
	fmt.Println(value)
	fmt.Println("*p2: ", *p2)
	*p1 = 54341234
	fmt.Println(value)
	fmt.Println("*p2: ", *p2)
}
複製代碼
你能夠使用一個星號(*)來解引用一個指針

運行unsafe.go,會獲得以下的輸出學習

*p1:  5
*p2:  5
5434123412312431212
*p2:  -930866580
54341234
*p2:  54341234
複製代碼

那麼這個輸出說明了什麼呢?它告訴了咱們,使用32-bit的指針沒法存一個64-bit的整數型ui

關於unsafe包

你已經實際操做過unsafe包的東西了,如今來看一下爲何這個庫這麼特別。spa

首先,若是你看了unsafe包的源碼,你可能會感到驚訝。在macOS Hight Sierra系統上,能夠使用Homebrew安裝Go 。unsafe源碼路徑在/usr/local/Cellar/go/1.9.1/libexec/src/unsafe/unsafe.go下面,不包含註釋,它的內容以下指針

$ cd /usr/local/Cellar/go/1.9.1/libexec/src/unsafe/
$ grep -v '^//' unsafe.go|grep -v '^$'
package unsafe
type ArbitraryType int
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
複製代碼

OK,其它的unsafe包的go代碼去哪裏了?答案很簡單:當你import到你程序裏的時候,Go編譯器實現了這個unsafe庫。code

許多系統庫,例如runtime,syscallos會常常使用到usafe

另外一個usafe包的例子

咱們經過一個moreUnsafe.go的小程序來了解unsafe庫的兼容性。moreUnsafe.go作的事情就是使用指針來訪問數組裏的全部元素。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	array := [...]int{0, 1, -2, 3, 4}
	pointer := &array[0]
	fmt.Print(*pointer, " ")
	memoryAddress := uintptr(unsafe.Pointer(pointer)) + unsafe.Sizeof(array[0])
	for i := 0; i < len(array)-1; i++ {
		pointer = (*int)(unsafe.Pointer(memoryAddress))
		fmt.Print(*pointer, " ")
		memoryAddress = uintptr(unsafe.Pointer(pointer)) + unsafe.Sizeof(array[0])
	}
複製代碼

首先,pointer變量指向array[0]的地址,array[0]是整型數組的第一個元素。接下來指向整數值的pointer變量會傳入unsafe.Pointer()方法,而後傳入uintptr。最後結果存到了memoryAddress裏。

unsafe.Sizeof(array[0])是爲了去訪問下一個數組元素,這個值是每一個元素佔的內存大小。每次for循環遍歷,都會把這個值加到memoryAddress上,這樣就能獲取到下一個數組元素的地址。*pointer的*符號對指針進行解引用,而後返回了所存的整數值。

後面部分代碼以下:

fmt.Println()
	pointer = (*int)(unsafe.Pointer(memoryAddress))
	fmt.Print("One more: ", *pointer, " ")
	memoryAddress = uintptr(unsafe.Pointer(pointer)) + unsafe.Sizeof(array[0])
	fmt.Println()
}
複製代碼

這裏,咱們嘗試使用指針和內存地址去訪問一個不存在的數組元素。因爲使用unsafe包,Go編譯器不會捕捉到這樣的邏輯錯誤,於是會產生一些不可預料的事情。

執行moreUnsafe.go,會產生以下的輸出:

$ go run moreUnsafe.go
0 1 -2 3 4 
One more: 824634191624 
複製代碼

如今,你使用指針訪問了Go數組裏的全部元素。可是,這裏真正的問題是,當你嘗試訪問無效的數組元素,程序並不會出錯而是會返回一個隨機的數字。

總結

unsafe的功能很強大,它能夠把任意指針轉換爲unsafe.Pointer指針,同時給了開發人員更多操做數據的手段。可是相對的,若是使用不當,則會形成不可預料的錯誤,這也是爲何這個包的名字被稱做unsafe的緣由,因此在你不肯定是否該使用unsafe操做的時候,儘可能不要使用它。


關於me

近期在胡神的慫恿下開了我的公衆號「羅小胖的技術筆記」,我在上面上傳了架構師之路的學習資料,僅供學習和參考,回覆「資料」就能夠免費獲取。同時也會在上面更新一些技術筆記以及平常總結和反思,歡迎關注和交流。

相關文章
相關標籤/搜索