golang unsafe 包閱讀
1. unsafe包
1.1. ArbitraryType
unsafe 包下定義了一個 ArbitratyType 類型,代表了任意的 Go 表達式。
type ArbitraryType int
1.2. Pointer
Pointer 定義:
type Pointer *ArbitraryType
Pointer 代表了一個指向任意類型的指針,有四種只適用對 Pointer 而不適用於其他類型的操作。
-
任意類型的指針值可以被轉換爲一個 Pointer
-
一個 Pointer 可以被轉換爲任意類型的指針值
-
一個 uintptr 可以被轉換爲一個 Pointer
-
一個 Pointer 也可以被轉換爲一個 uintptr
因此,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