Go 之 Unsafe 包

概述

在 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 非類型安全指針

函數

返回類型 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
}

返回結構體成員在內存中位置離結構體起始處的字節數,所傳遞的參數必須是結構體的成員

 
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 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變量

該函數返回一個字符串,底層字節從 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

}

StringData 返回一個指向 str 的底層字節的指針。對於空字符串,返回值未指定,可能爲 nil。由於 Go 字符串是不可變的,因此不能修改 StringData 返回的字節。

func demo5() {
 println(unsafe.StringData("hello")) // 0x100b2bfc5
}

函數 Slice 返回一個切片,其底層數組以 ptr 開頭,長度和容量爲 len,如果 ptr 爲 nil,len 爲零,Slice 將返回 nil。len 參數必須是整數類型或非類型常量。常量 len 參數必須是非負的,並且可以由 int 類型的值表示;如果它是一個非類型化常量,則它被賦予 int 類型。在運行時,如果 len 爲負,或者如果 ptr 爲 nil 且 len 不爲零,則會發生 panic。

此函數在一個 (非安全) 指針表示的地址上添加一個偏移量,然後返回一個新地址的指針‘

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([]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