go語言在設計上確保了一些安全的屬性,限制了程式可能出錯的途徑,例如嚴格的型別轉換規則,但也使得很多實作的細節無法通過go程式來訪問,例如對于聚合型別(如結構體)的記憶體布局,或者一個函式對應的機器碼,
這里我們將討論unsafe包,它是由編譯器實作的,實作了對語言內置特性的訪問功能,這些特性一般是不可見的,因為它們暴露了go詳細的記憶體布局,雖然包的名字叫unsafe,但是這些函式本身是安全的,并且在做記憶體優化的時候,它們對理解函式底層記憶體布局很有幫助,
unsafe.Sizeof
unsafe.Sizeof 報告傳遞給它的引數在記憶體中所占的位元組長度,這個引數可以是任意型別的運算式,Sizeof僅會報告每個資料結構固定部分的記憶體所占位元組長度,例如指標或者字串所占的長度,但不會報告例如字串內容的間接長度,為了可移植性,以字來表示參考型別的長度或者包含參考型別的長度,在32位系統上字的長度是4個位元組,而在64位系統上字的長度是8個位元組,
package main
import (
"fmt"
"unsafe"
)
func main() {
var x struct{
a bool
b int16
c []int
}
fmt.Println(unsafe.Sizeof(x.a))
fmt.Println(unsafe.Sizeof(x.b))
fmt.Println(unsafe.Sizeof(x.c))
fmt.Println(unsafe.Sizeof(x))
}
// 64位機器結果:
// 1 : 1個位元組,bool
// 2 : 兩個位元組,16/8 = 2
// 24 : 切片24個位元組,3個字,因為切片包含一個指標,一個長度,一個容量,
// 32 :前面兩個加上記憶體空位后就是一個字,8個位元組,所以 8 + 24 = 32
如果b和c交換位置,那么記憶體空位將會更大,8 + 24 + 8 = 40
func main() {
var x struct{
a bool
c []int
b int16
}
fmt.Println(unsafe.Sizeof(x.a))
fmt.Println(unsafe.Sizeof(x.c))
fmt.Println(unsafe.Sizeof(x.b))
fmt.Println(unsafe.Sizeof(x))
}
// 64位機器結果:
// 1
// 24
// 2
// 40 這里8 + 24 = 32和我們的猜想一樣,
unsafe.Alignof
unsafe.Alignof報告引數型別所要求的對齊方式,這個引數可以是任意型別的運算式,并回傳一個常量,布爾型別和數值型別對齊到它們的長度(最大8個位元組),其他型別按字對齊,
package main
import (
"fmt"
"unsafe"
)
func main() {
var x struct{
a bool
b int16
c []int
}
fmt.Println(unsafe.Alignof(x.a))
fmt.Println(unsafe.Alignof(x.b))
fmt.Println(unsafe.Alignof(x.c))
fmt.Println(unsafe.Alignof(x))
}
// 64位機器結果:
// 1 :這是布爾型別,布爾型別和數值型別對齊到它們長度
// 2 :這里數值型別,同上
// 8 :其他型別按字對齊,在64位機器上,一個字是8個位元組
// 8
unsafe.Offsetof(f)
計算成員f相對于結構體的起始地址的偏移量,如果有記憶體空位也計算在內,該函式的運算元,必須是一個成員選擇器:x.a,
func main() {
var x struct{
a bool
c []int
b int16
}
fmt.Println(unsafe.Offsetof(x.a))
fmt.Println(unsafe.Offsetof(x.c))
fmt.Println(unsafe.Offsetof(x.b))
}
// 64位機器結果:
// 0 : 結構體的第一個成員
// 8 :這里有7個位元組的記憶體空位
// 32
unsafe.Pointer
unsafe.Pointer是一種特殊型別的指標,它可以存盤任何變數的地址,對于一個unsafe.Pointer型別的指標,由于我們不知道它的具體型別,導致我們不能間接的通過*p來獲取它的實際值,普通型別的指標也可以轉換為unsafe.Pointer型別的指標,unsafe.Pointer型別的指標可以轉換為普通型別的指標,而且不必和原來的型別相同,使用unsafe.Pointer進行型別轉換可以將任意的值寫入記憶體,并因此破壞型別系統,
uintper型別
uintper型別保存了指標所指向地址的數值,這樣就可以進行數值運算,(uintpter型別是一個足夠大的無符號整型,可以用來表示任何地址,)unsafe.Pointer也可以轉換為uintptr,當然uintptr也可以轉換為unsafe.Pointer(這里也會破壞型別系統),
package main
import (
"fmt"
"unsafe"
)
func main() {
var x struct{
a bool
b int16
c []int
}
//pb := &x.b
pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
fmt.Println(x.b)
}
這里首先將x的地址轉換為unsafe.Pointer型別從而轉換為uintptr型別,uintptr型別就可以用于計算,計算后再轉換為原來的型別,對記憶體地址指向的區域賦值,但是這里不能引入uintptr型別的臨時變數,例如下面這樣,因為垃圾回收器會移動記憶體中的變數,為了減少記憶體碎片,但是在垃圾回收器uintptr型別僅僅是一個數值,所以移動過后,不會改變uintptr存的指標對應的記憶體里的資料,
ptr := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(ptr))
總結
unsafe包可以用來操作記憶體,從而增加go語言的靈活性,但是unsafe包無法保證在未來go語言升級中能夠兼容,uintptr型別不能作為臨時變數,并且在uintptr型別轉換到unsafe.Pointer的程序中要盡量減少uintptr的操作次數,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/435324.html
標籤:Go
上一篇:Goland的GC回識訓制
