unsafe內容介紹
type ArbitraryType inttype Pointer *ArbitraryTypefunc Sizeof(x ArbitraryType) uintptrfunc Offsetof(x ArbitraryType) uintptrfunc Alignof(x ArbitraryType) uintptr
unsafe包只有兩個類型,三個函數,但是功能很強大。
unsafe 庫讓 golang 可以像C語言一樣操作電腦記憶體,但這並不是golang推薦使用的,能不用盡量不用,就像它的名字所表達的一樣,它繞過了golang的記憶體安全原則,是不安全的,容易使你的程式出現莫名其妙的問題,不利於程式的擴充與維護。
先簡單介紹下Golang指標
*類型:普通指標,用於傳遞對象地址,不能進行指標運算。
unsafe.Pointer:通用指標類型,用於轉換不同類型的指標,不能進行指標運算。
uintptr:用於指標運算,GC 不把 uintptr 當指標,uintptr 無法持有對象。uintptr 類型的目標會被回收。
unsafe.Pointer 可以和 普通指標 進行相互轉換。
unsafe.Pointer 可以和 uintptr 進行相互轉換。
也就是說 unsafe.Pointer 是橋樑,可以讓任意類型的指標實現相互轉換,也可以將任意類型的指標轉換為 uintptr 進行指標運算。
uintptr這個類型,在golang中,位元組長度也是與int一致。通常Pointer不能參與運算,比如你要在某個指標地址上加上一個位移量,Pointer是不能做這個運算的,那麼誰可以呢?就是uintptr類型了,只要將Pointer類型轉換成uintptr類型,做完加減法後,轉換成Pointer,通過*操作,取值,修改值,隨意。
兩個類型簡介
// ArbitraryType is here for the purposes of documentation only and is not actually// part of the unsafe package. It represents the type of an arbitrary Go expression.type ArbitraryType inttype Pointer *ArbitraryType
ArbitraryType是int的一個別名,在Go中對ArbitraryType賦予特殊的意義。代表一個任意Go運算式類型。
Pointer 是int指標類型的一個別名,在Go中可以把Pointer類型,理解成任何指標的父類型。
下面為官方文檔中對Pointer的使用情境介紹
Pointer represents a pointer to an arbitrary type. There are four special operationsavailable for type Pointer that are not available for other types:- A pointer value of any type can be converted to a Pointer.- A Pointer can be converted to a pointer value of any type.- A uintptr can be converted to a Pointer.- A Pointer can be converted to a uintptr.Pointer therefore allows a program to defeat the type system and read and write arbitrary memory. It should be used with extreme care.
golang的指標類型長度與int類型長度,在記憶體中佔用的位元組數是一樣的.
ArbitraryType類型的變數也可以是指標。所以,千萬不要死磕type後邊的那個int
三個函數簡介
// Sizeof takes an expression x of any type and returns the size in bytes// of a hypothetical variable v as if v was declared via var v = x.// The size does not include any memory possibly referenced by x.// For instance, if x is a slice, Sizeof returns the size of the slice// descriptor, not the size of the memory referenced by the slice.**func Sizeof(x ArbitraryType) uintptr**// Offsetof returns the offset within the struct of the field represented by x,// which must be of the form structValue.field. In other words, it returns the// number of bytes between the start of the struct and the start of the field.**func Offsetof(x ArbitraryType) uintptr**// Alignof takes an expression x of any type and returns the required alignment// of a hypothetical variable v as if v was declared via var v = x.// It is the largest value m such that the address of v is always zero mod m.// It is the same as the value returned by reflect.TypeOf(x).Align().// As a special case, if a variable s is of struct type and f is a field// within that struct, then Alignof(s.f) will return the required alignment// of a field of that type within a struct. This case is the same as the// value returned by reflect.TypeOf(s.f).FieldAlign().**func Alignof(x ArbitraryType) uintptr**
通過分析發現,這三個函數的參數均是ArbitraryType類型,就是接受任何類型的變數。
- Alignof返回變數對齊位元組數量
- Offsetof返回變數指定屬性的位移量,這個函數雖然接收的是任何類型的變數,但是有一個前提,就是變數要是一個struct類型,且還不能直接將這個struct類型的變數當作參數,只能將這個struct類型變數的屬性當作參數。
- Sizeof 返回變數在記憶體中佔用的位元組數,切記,如果是slice,則不會返回這個slice在記憶體中的實際佔用長度。
樣本
Sizeof
unsafe.Sizeof函數返回的就是uintptr類型的值(運算式,即值的大小):
var p float64 = 99fmt.Println(reflect.TypeOf(unsafe.Sizeof(p)))fmt.Println(unsafe.Sizeof(p))
results:
uintptr8
unsafe.Sizeof接受任意類型的值(運算式),返回其佔用的位元組數,在上面的例子中float64的大小是8bytes。
如果傳入一個指標類型的對象會返回多少呢?
type W struct { a byte b int32 c int64}var w *Wfmt.Println(unsafe.Sizeof(w)) //4 or 8
一般情況下,可能是4或8,因為w是指標類型uintptr,而uintptr是平台相關的,在32位系統下大小是4bytes,在64位系統下是8bytes。
要擷取實值型別的大小,需要對指標變數進行取值:
fmt.Println(unsafe.Sizeof(*w)) //16
對齊
在上面的例子中,*w的大小為16,按照常理來說,byte佔用1位元組,int32佔用4位元組,int64佔用8位元組,大小應該是13才對。這是因為發生了對齊,unsafe.Alignof可以計算對齊值:
unsafe.Alignof(w.a) // type byteunsafe.Alignof(w.b) // type int32 unsafe.Alignof(w.c) // type int64
分別是1、4、8,因為int32類型的對齊值是4bytes,必須是4的倍數,故byte類型要填充3個位元組。而填充後,兩者的大小和為8bytes,int64對齊值是8bytes,不需要填充,所以用unsafe.Sizeof擷取到結構的大小為4+4+8=16。
反射包的對齊方法
反射包也有某些方法可用於計算對齊值:
unsafe.Alignof(w)等價於reflect.TypeOf(w).Align。
unsafe.Alignof(w.i)等價於reflect.Typeof(w.i).FieldAlign()。
結構體的對齊值
如果我們計算的是結構體的對齊值而不是某個欄位或者基本類型,那麼值會是多少呢?
type W struct { a byte b int32 c int64}var w *Wvar w2 Wfmt.Println(unsafe.Alignof(w))fmt.Println(unsafe.Alignof(w2))fmt.Println(reflect.TypeOf(w).Elem().Align())
results:
888
64位機器下,指標對象的對齊值是8,因為指標類型是uintptr。而結構體的實值型別卻是8bytes的對齊值,這是因為會先進列欄位的對齊,欄位最大的對齊值是8bytes,因此結構體實值型別的對齊值也是8。
更改結構,驗證一下:
type W struct { a byte b int32 c int32}var w Wfmt.Println(unsafe.Alignof(w)) //4
綜合樣本
type T struct { t1 byte t2 int32 t3 int64 t4 string t5 bool}fmt.Println("----------unsafe.Pointer---------")t := &T{1, 2, 3, "this is a example", true}ptr := unsafe.Pointer(t)t1 := (*byte)(ptr)fmt.Println(*t1)t2 := (*int32)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(t.t2)))*t2 = 99fmt.Println(t)t3 := (*int64)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(t.t3)))fmt.Println(*t3)*t3 = 123fmt.Println(t)
results:
----------unsafe.Pointer---------1&{1 99 3 this is a example true}3&{1 99 123 this is a example true}
藉助於 unsafe.Pointer,我們實現了像 C 語言中的指標位移操作。可以看出,這種不安全的操作使得我們可以在任何地方直接存取結構體中未公開的成員,只要能得到這個結構體變數的地址。
參考資料:
- https://blog.csdn.net/libing_thinking/article/details/49907815
- https://studygolang.com/articles/6903
- https://blog.csdn.net/hzwy23/article/details/60893765
- https://www.cnblogs.com/golove/p/5909968.html
- https://studygolang.com/articles/1414
作者:ycyoes
連結:https://www.jianshu.com/p/c85fc3e31249
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。