Go 之 Unsafe 包
概述
在 Go 中,我們常見的指針是類型安全的,但是存在很多的限制。比如:
-
Go 的指針不能進行數學運算
-
不同類型的指針不能進行相互轉換
-
不同類型的指針不能用
==
或者!=
比較 -
不同類型的指針不能相互賦值
但是在 Go 中也存在非類型安全的指針,比如說 unsafe.Pointer。
unsafe 包中包含繞過 Go 程序的類型安全的操作。導入不安全的包可能是不可移植的,並且不受 Go 1 兼容性指南的保護。
此文章是基於 go1.20 版本進行介紹的,不同的 go 版本,該包中的方法會有些不同
在 go1.20 的版本中,unsafe 包的結構是
func Alignof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Sizeof(x ArbitraryType) uintptr
func String(ptr *byte, len IntegerType) string (Add in go 1.20)
func StringData(str string) *byte (Add in go 1.20)
type ArbitraryType
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType (Add in go 1.17)
func SliceData(slice []ArbitraryType) *ArbitraryType (Add in go 1.20)
type IntegerType
type Pointer
func Add(ptr Pointer, len IntegerType) Pointer (Add in go 1.17)
unsafe.Pointer
type Pointer *ArbitraryType
type ArbitraryType int
這裏的ArbitraryType
它表示 Go 用來表示任意類型的一個簡寫,它並不表示任何一個類型
Pointer
非類型安全指針
-
任何類型的指針都可以與
Pointer
相互轉換 -
uintptr 和
Pointer
可以相互轉換
函數
- func Sizeof(x ArbitraryType) uintptr
返回類型 x 所佔的字節數,但不包含 x 所指向的內容大小,對於一個 slice 來說返回的就是 slice header 的大小
func demo1() {
a := int(1)
b := float32(2.0)
s := "Ethan"
sl := make([]string, 16)
println(unsafe.Sizeof(a)) // 8
println(unsafe.Sizeof(b)) // 4
println(unsafe.Sizeof(s)) // 16
println(unsafe.Sizeof(sl)) // 24
}
- func Offsetof(x ArbitraryType) uintptr
返回結構體成員在內存中位置離結構體起始處的字節數,所傳遞的參數必須是結構體的成員
type User struct {
Name string
Nickname string
Age int
}
func demo2() {
u := User{
Age: 10,
Name: "Ethan",
Nickname: "Leo",
}
println(unsafe.Offsetof(u.Name)) // 0
println(unsafe.Offsetof(u.Nickname)) // 16
println(unsafe.Offsetof(u.Age)) // 32
}
- func Alignof(x ArbitraryType) uintptr
返回的值是指當類型進行內存對齊時,它分配到的內存地址可以整除該函數的返回值
func demo3() {
a := 1
b := int8(8)
s := "1"
println(unsafe.Alignof(a)) // 8
println(unsafe.Alignof(b)) // 1
println(unsafe.Alignof(s)) // 8
}
以上三個函數返回的結果都是uintptr
類型,這個類型可以和unsafe.Pointer
類型相互轉換。由於三個函數都在編譯期間執行,所以它們的結果可以直接賦值給const
變量
- func String(ptr *byte, len IntegerType) string
該函數返回一個字符串,底層字節從 ptr 開始,長度爲 len。len 參數必須是證書類型或者是無類型常量,常量 len 參數必須是非負的並且可以用 int 類型的值來表示。如果在運行時 len 爲負,或者 ptr 爲 nil 且 len 不爲零,那麼會發生 panic
func demo4() {
b := []byte{'h', 'e', 'l', 'l', 'o'}
println(unsafe.String(&b[0], len(b))) // hello
}
- func StringData(str string) *byte
StringData 返回一個指向 str 的底層字節的指針。對於空字符串,返回值未指定,可能爲 nil。由於 Go 字符串是不可變的,因此不能修改 StringData 返回的字節。
func demo5() {
println(unsafe.StringData("hello")) // 0x100b2bfc5
}
- func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
函數 Slice 返回一個切片,其底層數組以 ptr 開頭,長度和容量爲 len,如果 ptr 爲 nil,len 爲零,Slice 將返回 nil。len 參數必須是整數類型或非類型常量。常量 len 參數必須是非負的,並且可以由 int 類型的值表示;如果它是一個非類型化常量,則它被賦予 int 類型。在運行時,如果 len 爲負,或者如果 ptr 爲 nil 且 len 不爲零,則會發生 panic。
- func Add(ptr Point, len IntegerType) Pointer
此函數在一個 (非安全) 指針表示的地址上添加一個偏移量,然後返回一個新地址的指針‘
func demo6() {
a := [16]int{3: 3, 9: 9, 11: 11}
fmt.Println(a)
eleSize := int(unsafe.Sizeof(a[0]))
p9 := &a[9]
up9 := unsafe.Pointer(p9)
p3 := (*int)(unsafe.Add(up9, -6*eleSize))
fmt.Println(*p3) // 3
s := unsafe.Slice(p9, 5)[:3]
fmt.Println(s) // [9 0 11]
fmt.Println(len(s), cap(s)) // 3 5
t := unsafe.Slice((*int)(nil), 0)
fmt.Println(t == nil) // true
// 下面是兩個不正確的調用。因爲它們
// 的返回結果引用了未知的內存塊。
_ = unsafe.Add(up9, 7*eleSize)
_ = unsafe.Slice(p9, 8)
}
使用
String 和 []byte 相互轉換
func ByteSlice2String(b []byte) string {
return unsafe.String(&b[0], len(b))
}
func String2ByteSlice(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
修改未導出的成員變量
package order
type User struct {
Id int
name string
}
func (u *User) Name() string {
return u.name
}
func demo7() {
u := order.User{
Id: 1,
}
println(u.Name())
n := (*string)(unsafe.Add(unsafe.Pointer(&u), unsafe.Sizeof(u.Id)))
*n = "Modify by unsafe"
println(u.Name())
}
獲取 Slice 和 Map 的長度
獲取 slice 的長度
// runtime/slice.go
// type slice struct {
// array unsafe.Pointer
// len int
// cap int
// }
// 獲取slice的長度
func demo8() {
s := make([]int, 9, 20)
l := (*int)(unsafe.Add(unsafe.Pointer(&s), uintptr(8)))
println(*l)
}
獲取 map 的長度
// runtime/map.go
//
// // A header for a Go map.
//
// type hmap struct {
// // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
// // Make sure this stays in sync with the compiler's definition.
// count int // # live cells == size of map. Must be first (used by len() builtin)
// flags uint8
// B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
// noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
// hash0 uint32 // hash seed
//
// buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
// oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
// nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
//
// extra *mapextra // optional fields
// }
func demo9() {
m := make(map[int]string)
l := (**int)(unsafe.Pointer(&m))
fmt.Println(**l)
fmt.Println(len(m))
}
參考資料
深度解密 Go 語言之 Unsafe
Type-Unsafe Pointers
官方文檔
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/HG5WaMGgzyt4svLsJhIz5A