golang unsafe 包閱讀

1.  unsafe包

1.1.  ArbitraryType

unsafe 包下定義了一個 ArbitratyType 類型,代表了任意的 Go 表達式。

 type ArbitraryType int

1.2.  Pointer

Pointer 定義:

 type Pointer *ArbitraryType

Pointer 代表了一個指向任意類型的指針,有四種只適用對 Pointer 而不適用於其他類型的操作。

因此,Pointer 可以跳過類型系統而直接指向任意類型。所以需要十分小心的使用。

關於使用 Pointer 的規則,不使用這些規則的代碼是不可用的,或者在未來是不可用的。

1.2.1.  使用 Pointer 作爲中間者將 *T1 轉換爲 *T2

前提是 T2 的大小不超過 T1,而且兩者的內存分佈相同。

func Float64bits(f float64) uint64 {
  return *(*uint64)(unsafe.Pointer(&f))
}

1.2.2.  把 Pointer 轉換爲 uintptr

把 Pointer 轉換爲 uintptr 將產生一個指向類型值的 int 變量。常用來打印一個 uintptr。

將 uintptr 轉換爲 Pointer 是不可用的。

因爲 uintptr 是一個整數值,而不是引用。就是說 uintptr 和指針沒有任何關係。可以說是將 Pointer 指向的地址的值返回給 uintptr,即使 uintptr 中的值對應的地址的對象更新了或者刪除了,uintptr 也不會改變。

1.2.3.  把 Pointer 轉爲 uintptr 再轉換回 Pointer,其中帶有 uintptr 數值運算

如果 Pointer 指向一個分配的對象,那麼如下轉換可以把 Pointer 指針向後移動。

 p = unsafe.Pointer(uintptr(p) + offset)

最常用的是指向結構體中不同字段或者數組中的元素

 // equivalent to f := unsafe.Pointer(&s.f)
 f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
 // equivalent to e := unsafe.Pointer(&x[i])
 e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))

這可以用來向前或向後移動指針,通過加或者減 offset。指針移動之後,也應該指向該內存範圍中。

將 Pointer 移動超過其對象的原始內存分配範圍是不可用的,如:

 // INVALID: end points outside allocated space.
 var s thing
 end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))
 // INVALID: end points outside allocated space.
 b := make([]byte, n)
 end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))

當然如下代碼也是錯誤的,因爲 uintptr 不可以儲存在變量中:

 // INVALID: uintptr cannot be stored in variable
 // before conversion back to Pointer.
 u := uintptr(p)
 p = unsafe.Pointer(u + offset)

Pointer 必須指向一個已經分配好的對象,而不能是 nil

 // INVALID: conversion of nil pointer
 u := unsafe.Pointer(nil)
 p := unsafe.Pointer(uintptr(u) + offset)

1.2.4.  當調用 syscall.Syscall 時,需要把 Poiner 轉換爲 uintptr

syscall 包下的 Syscall 函數把 uintptr 參數傳遞給操作系統,然後根據調用的相關信息,把相應的 uintptr 再轉換爲指針。

如果一個指針參數必須被轉換爲 uintptr 作爲參數的話,這個轉換隻能在調用函數中的參數表達式完成,因爲 uintptr 是不能儲存在變量中的。

 syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

編譯器處理函數調用中的指針時,該指針所指向的對象會被保留到函數調用結束,即使該對象在函數調用時並不使用。

如下是錯誤的代碼,因爲 uintptr 不能保存在變量中

 // INVALID: uintptr cannot be stored in variable
 // before implicit conversion back to Pointer during system call.
 u := uintptr(unsafe.Pointer(p))
 syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))

1.2.5.  將 reflect.Value.Pointer 或者 reflect.Value.UnsafeAddr 的結果從 uintptr 轉換爲 Pointer

包 reflect 下 Value 的 Pointer 方法和 UnsafeAddr 方法返回的是 uintptr 而不是 Pointer 類型,以便於調用者不使用 usafe 包就可以轉換爲任意類型。這也意味着,這兩個方法的返回值必須使用 Pointer 進行轉換纔可以使用:

 p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))

因爲這兩個函數調用的返回值是 uintptr,所以也是不可以變量儲存的。

1.2.6.  reflect.SliceHeader 或者 reflect.StringHeader 的 Data 字段同 Pointer 的相互轉換

前面說過,返回 uintptr 是爲了調用者可以直接進行不同類型的轉換,而不用導入 unsafe 包。這意味着,只有當指針解析爲切片或者字符串時 SliceHeader 和 StringHeader 纔可以被使用。

 var s string
 hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1
 hdr.Data = uintptr(unsafe.Pointer(p))              // case 6 (this case)
 hdr.Len = n

通常情況下,SliceHeader 和 StringHeader 只能作爲 *SliceHeader 和 *StringHeader 使用,而不可以使用其結構體形式。

 // INVALID: a directly-declared header will not hold Data as a reference.
 var hdr reflect.StringHeader
 hdr.Data = uintptr(unsafe.Pointer(p))
 hdr.Len = n
 s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost

1.3.  Sizeof 函數


定義:

 func Sizeof(x ArbitraryType) uintptr

直接複製標準文檔中的內容,下同。

Sizeof 返回類型 v 本身數據所佔用的字節數。返回值是 “頂層” 的數據佔有的字節數。例如,若 v 是一個切片,它會返回該切片描述符的大小,而非該切片底層引用的內存的大小。

1.4.  Alignof

定義:

 func Alignof(v ArbitraryType) uintptr

Alignof 返回類型 v 的對齊方式(即類型 v 在內存中佔用的字節數);若是結構體類型的字段的形式,它會返回字段 f 在該結構體中的對齊方式。

1.5.  Offsetof

定義:

 func Offsetof(v ArbitraryType) uintptr

Offsetof 返回類型 v 所代表的結構體字段在結構體中的偏移量,它必須爲結構體類型的字段的形式。換句話說,它返回該結構起始處與該字段起始處之間的字節數。

總結

1.2 中的 Pointer 和 uintptr 的區別:

假設在內存中有一個變量a := 1

那麼p := Pointer(&a)中,p 包含的就是 a 的實際地址,假設爲1000,當 a 在內存中移動時,p 中的地址值也會實時更新。

uintprt(p)只是1000,就是 a 的地址值,但是當 a 在內存中移動時,原來獲取的uintptr值並不會發生變化,一直都是 1000。

也是因爲這個原因,syscall.Syscall 傳入的 uintptr 如果代表一個對象的指針,那麼該對象在內存中是一直被保留的,而且不能移動,否則的話 uintptr 指向的就不是原來的對象了,容易內存泄漏。

還有一個就是 uintptr 不能保存在變量中,只能使用 Pointer 進行轉換然後才能保存。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/He7gwIla7wrRW22ZiF1pQQ