go 原始碼-unsafe包
go 原始碼閱讀-unsafe包
unsafe包包含了go程式設計中繞過型別安全的操作. import unsafe的包大多是非直接可用並且不受gov1相容性指南約束的
type ArbitraryType int
只是為了文件而宣告的型別,實際上它並不是unsafe包的一部分,它代表任意go表示式的型別
type Pointer *ArbitraryType
Pointer代表了任意型別對應的指標,有四種特殊的操作只對Pointer型別生效,對其他型別無效 (1) 任意型別的指標可以轉為Pointer (2) Pointer可以轉為任意型別 (3)uint型別指標可以轉為指標 (4)Pointer可以轉為uint型別指標因此Pointer允許程式無視type體系對任意型別記憶體進行讀寫。所以使用它的時候要格外小心
在下面的這些正規化中是可以使用Pointer的:
不使用這些正規化的程式碼目前是(或者未來會是)不可用的即使是下面的正規化也會有嚴重警告:warning:
執行go vet
能幫助查詢到Pointer不符合正規化 的使用,但是go vet
不保證程式碼可用1.
Conversion of a *T1 to Pointer to *T2.
T1和T2需要有相同的記憶體佈局,這種轉換允許reinterpreting(重新解釋)date從一種型別到另一種型別,一個math.Float64bits的實現如下:
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
2.將Pointer轉為uintptr (but not back to Pointer).
uintptr 是一種無符號的整數型別,沒有指定具體的bit大小但是足以容納指標。 uintptr型別只有在底層程式設計是才需要,特別是Go語言和C語言函式庫或作業系統介面相互動的地方。
通常uintptr不可以迴轉為Pointer
uintptr是一個integer型別,不是引用。
將一個Pointer轉為uintptr時會新建一個無pointer語義的integer值,這種uintptr通常用做print
即使uintptrl持有某個物件的地址,當這個物件被移走時gc並不會更新uintptr的值,而且uintptr也不能防止它被回收。
後面的正規化列出了唯一從uintptr到Pointer可行的轉化
**3.**通過計算在Pointer和uintptr之間相互轉換
如果p指向一個集合中的物件,那麼它可以轉換為uintptr然後增加offset從而指向該物件後面的物件,而且能再轉換回Pointerp= unsafe.Pointer(uintptr(p)+offset)
這種正規化最常見的用法就是獲取一個struct中的屬性(field)或者陣列中的元素(element)
示例1
e := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
上面等價於:e :=unsafe.Pointer(&s.f)
示例2
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
上面等價於e := unsafe.Pointer(&x[i])
通過這種方式可以增加或減小Pointer所指向的offset。而且我們可以在pointer上使用位運算子(&^)。但在任何情況下,pointer最終都必須指向原來的那個集合中的物件
與在c語言中不同,pointer不可以跨過集合物件的最後一個元素 下面的示例是錯誤的
b := make([]byte,n) end := unsafe.Pointer(uintptr(unsafe.Pointer(&b)) + uintptr(n))
另外需要注意的是兩種轉換必須在同一個只有他們兩者演算法關係的表示式中,下面的是錯誤示例
u := uintptr(p) p := unsafe.Pointer(u + offset)
4.當呼叫 syscall.Syscall時將Pointer轉換為uintptr
在syscall包中的Syscall方法將直接傳遞uintptr引數給作業系統層,然後他們中的一部分將按照呼叫時的具體情況被重新解釋(reinterpret)為Pointer.
如果一個pointer引數必須被轉換為uintptr型別使用,那麼我們必須要在呼叫的表示式中顯式的進行轉換:syscall.Syscall(SYS_READ,uintptr(fd), uintptr(unsafe.Pointer(p)),uintptr(n))
編譯器會將呼叫具體方法時的引數列表中pointer轉換為uintptr的引數裝載進引用物件集合,並且它將不會被銷燬直到真個呼叫完成,即使在方法體中不再被用到。
為了讓編譯器識別這個正規化,轉換必須顯示出現在引數列表,下面是錯誤示例
u := uintptr(unsafe.Pointer(p)) syscall.Syscall(SYS_READ,uintptr(fd),u,uintptr(n))
**5.**可以將reflect.Value.Pointer,reflect.Value.UnsafeAddr從uintptr轉換為Pointer
reflect包中的Value的方法可以使Pointer或者UnsafeAddr返回uintptr型別來替代unsafe.Pointer型別,使得呼叫者不需要匯入unsafe包就能讓返回值是任意型別,這意味著返回結果是脆弱的必須在呼叫後立刻轉為Pointer型別,呼叫和轉換必須在同一個表示式內完成
p := (*int(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
**6.**將reflect.SliceHeader或者reflect.StringHeader 的Date屬性與Pointer相互轉換。
前面提到,reflect的資料型別SliceHeader和 StringHeader將資料宣告為uintptr型別從而使的呼叫者不需要匯入unsafe包就可以改變返回值的型別。而這意味著一個真正的slice或者string型別的值只能被重新編譯成SliceHeader和StringHeader
var s string hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) hdr.Data = uintptr(unsafe.Pointer(p)) hdr.Len = n
在這個用法中hdr.Data是一種獲取slice型別底層頭指標的替代方案,它並不是uintptr型別 總而言之,reflect.SliceHeader 和 reflect.StringHeader只能作為*reflect.SliceHeader和 *reflect.StringHeader時指向string和slice型別,他們的非指標形態是無效的。 在我們程式碼中不應該直接宣告他們的結構體,下面是錯誤示例:
var hdr reflect.StringHeader hdr.Data = uintptr(unsafe.Pointer(p)) hdr.Len = n s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost
func Sizeof(x ArbitraryType) uintptr
Sizeof方法返回物件x所佔有的的記憶體大小(byte為單位),不包含x中引用型別所佔有的記憶體大小
func Offsetof(x ArbitraryType) uintptr
該方法返回x所在結構體的起始記憶體地址到x所對應屬性兩者距離,單位為byte,引數x的格式應該是structValue.field
func Alignof(x ArbitraryType) uintptr
參考資料ofollow,noindex" target="_blank">記憶體對齊 等價於reflect.TypeOf(x).Align()或reflect.TypeOf(s.f).FieldAlign() #go/unsafe