本文分享自華爲雲社區《突破語言golang中的類型限制》,作者:碼樂。
1 簡介
在使用c語言編程時,常常因爲類型的問題大傷腦筋,而其他語言比如java,python默認類型又是難以改變的,golang提供了一些方式用於喜歡hack的用戶。
2 標準庫unsafe的簡單介紹
官方說明標準庫 unsafe 包含繞過 Go 程序的類型安全的操作。
導入unsafe包可能是不可移植的,並且不受 Go 1 兼容性指南的保護。
在1.20中,標準庫的unsafe包很小, 二個結構體類型,八個函數,在一個文件中。
package unsage type ArbitraryType int type IntegerType int type Pointer *ArbitraryType func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte
unsafe包定義了 二個類型和 八個函數,二個類型 ArbitraryType 和 IntegerType 不真正屬於unsafe包,我們在Go代碼中並不能使用它們定義變量。
它表示一個任意表達式的類型,僅用於文檔目的,Go編譯器會對其做特殊處理。
雖然位於 unsafe,但是 Alignof,Offsetof,Sizeof,這三個函數的使用是絕對安全的。 以至於Go設計者Rob pike提議移走它們。
這三個函數的共同點是 都返回 uintptr 類型。
之所以使用 uintptr 類型而不是 uint64 整型,因爲這三個函數更多應用於 有 unsafe.Pointer和 uintptr類型參數的指針運算。
採用uintptr做爲返回值類型可以減少指針運算表達式的顯式類型轉換。
2.1 獲取大小 Sizeof
Sizeof 用於獲取一個表達式的大小。 該函數獲取一個任意類型的表達式 x,並返回 按bytes計算 的大小,假設變量v,並且v通過 v =x聲明。
Sizeof 接收任何類型的表達式x,並返回以bytes字節爲單位的大小, 並且假設變量v是通過var v = x聲明的。該大小不包括任何可能被x引用的內存。
例如,如果x是一個切片,Sizeof返回切片描述符的大小,而不是該片所引用的內存的大小。
對於一個結構體,其大小包括由字段對齊引入的任何填充。
如果參數x的類型沒有變化,不具有可變的大小,Sizeof的返回值是一個Go常數不可變值 。
(如果一個類型是一個類型參數,或者是一個數組,則該類型具有可變的大小或結構類型中的元素大小可變)。
示例:
var ( i int = 5 a = [10]int{} ss = a[:] f FuncFoo preValue = map[string]uintptr{ "i": 8, "a": 80, "ss": 24, "f": 48, "f.c": 10, "int_nil": 8, } ) type FuncFoo struct { a int b string c [10]byte d float64 } func TestFuncSizeof(t *testing.T) { defer setUp(t.Name())() fmt.Printf("\tExecute test:%v\n", t.Name()) if unsafe.Sizeof(i) != preValue["i"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t) } if unsafe.Sizeof(a) != preValue["a"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["a"]), t) } if unsafe.Sizeof(ss) != preValue["ss"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["ss"]), t) } if unsafe.Sizeof(f) != preValue["f"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f"]), t) } if unsafe.Sizeof(f.c) != preValue["f.c"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f.c"]), t) } if unsafe.Sizeof(unsafe.Sizeof((*int)(nil))) != preValue["int_nil"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } }
Sizeof 函數不支持之間傳入無類型信息的nil值,如下錯誤
unsafe.Sizeof(nil)
我們必須顯式告知 Sizeof 傳入的nil究竟是那個類型,
unsafe.Sizeof(unsafe.Sizeof((*int)(nil)))
必須顯式告知nil是哪個類型的nil,這就是傳入一個值 nil 但是類型明確的變量。
對齊係數 Alignof 用於獲取一個表達式的內地地址對齊係數,對齊係數 alignment factor 是一個計算機體系架構 computer architecture 層面的術語。
在不同計算機體系中,處理器對變量地址都有對齊要求,即變量的地址必須可被該變量的對齊係數整除。
它接收一個任何類型的表達式x,並返回所需的排列方式 假設變量v是通過var v = x聲明的。
它是m一個最大的值。
例1,
a = [10]int{} reflect.TypeOf(x).Align() //8 unsafe.Alignof(a) //8
它與reflect.TypeOf(x).Align()返回的值相同。
作爲一個特例,如果一個變量s是結構類型,f是一個字段,那麼Alignof(s.f)將返回所需的對齊方式。
該類型的字段在結構中的位置。這種情況與reeflect.TypeOf(s.f).FieldAlign()返回的值。
Alignof的返回值是一個Go常數,如果參數的類型不具有可變大小。
(關於可變大小類型的定義,請參見[Sizeof]的描述)。
繼上 例2:
var ( i int = 5 a = [10]int{} ss = a[:] f FuncFoo zhs = "文" preValue = map[string]uintptr{ "i": 8, "a": 80, "ss": 24, "f": 48, "f.c": 10, "int_nil": 8, } ) func TestAlignof(t *testing.T) { defer setUp(t.Name())() fmt.Printf("\tExecute test:%v\n", t.Name()) var x int b := uintptr(unsafe.Pointer(&x))%unsafe.Alignof(x) == 0 t.Log("alignof:", b) if unsafe.Alignof(i) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(a) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(ss) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(f.a) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) } if unsafe.Alignof(f) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t) }
中文對齊係數 爲 8
if unsafe.Alignof(zhs) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t) }
空結構體對齊係數 1
if unsafe.Alignof(struct{}{}) != 1 { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t) }
byte 數組對齊係數爲 1
if unsafe.Alignof(sbyte) != 1 { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t) }
長度爲0 的數組,與其元素的對齊係數相同
if unsafe.Alignof([0]int{}) != 8 { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 8), t) }
長度爲0 的數組,與其元素的對齊係數相同
if unsafe.Alignof([0]struct{}{}) != 1 { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t) } }
執行它:
go test -timeout 30s -run ^TestAlignof$ ./unsafe_case.go
對齊係數 alignment factor,變量的地址必須可被該變量的對齊係數整除。
2.2 使用對齊的例子
我們使用相同字段,分別創建兩個結構體屬性分別爲對齊或不對齊,幫助 go 更好地分配內存和 使用cpu讀取,查看效果
type RandomResource struct { Cloud string // 16 bytes Name string // 16 bytes HaveDSL bool // 1 byte PluginVersion string // 16 bytes IsVersionControlled bool // 1 byte TerraformVersion string // 16 bytes ModuleVersionMajor int32 // 4 bytes } type OrderResource struct { ModuleVersionMajor int32 // 4 bytes HaveDSL bool // 1 byte IsVersionControlled bool // 1 byte Cloud string // 16 bytes Name string // 16 bytes PluginVersion string // 16 bytes TerraformVersion string // 16 bytes }
字段 存儲使用的空間與 字段值沒有關係
var d RandomResource d.Cloud = "aws-singapore" ... InfoHandler(fmt.Sprintf("隨機順序屬性的結構體內存 總共佔用 StructType: %T => [%d]\n", d, unsafe.Sizeof(d)), m) var te = OrderResource{} te.Cloud = "aws-singapore" ... m.Logf("屬性對齊的結構體內存 總共佔用 StructType:d %T => [%d]\n", te, unsafe.Sizeof(te))
然後複製結構體,並改變其屬性值,查看存儲空間和值的長度變化
te2 := te te2.Cloud = "ali2" m.Logf("結構體2 te2:%#v\n", &te2) m.Logf("結構體1 te:%#v\n", &te) m.Log("改變 te3 將同時改變 te,te3 指向了 te的地址") m.Log("複製了對齊結構體,並重新賦值,用於查看字段長度。") m.Log("(*te).Cloud:", (te).Cloud, "*te.Cloud", te.Cloud, "te size:", unsafe.Sizeof(te.Cloud), "te value len:", len(te.Cloud)) te3 := &te te3.Cloud = "HWCloud2" m.Log("(*te3).Cloud:", (*te3).Cloud, "*te3.Cloud", te3.Cloud, "te3 size:", unsafe.Sizeof(te3.Cloud), "te3 value len:", len(te3.Cloud)) m.Logf("字段 Cloud:%v te3:%p\n", (*te3).Cloud, te3) m.Logf("字段 Cloud:%v order:%v te:%v, addr:%p\n", te.Cloud, (te).Cloud, te, &te)
執行它,
go test -v .\case_test.go
得到以下輸出:
隨機順序屬性的結構體內存 總共佔用 StructType: main.Raesource => [88]
...
屬性對齊的結構體內存 總共佔用 StructType:d main.OrderResource => [72]
改變 te3 將同時改變 te,te3 指向了 te的地址
case_test.go:186: 複製了對齊結構體,並重新賦值,用於查看字段長度。 case_test.go:188: (*te).Cloud: aws-singapore *te.Cloud aws-singapore te size: 16 te Alignof: 8 te value len: 13 reflect Align len and field Align len: 8 8 case_test.go:190: (*te2).Cloud: ali2 *te2.Cloud aws-singapore te2 size: 16 te2 Alignof: 8 te2 value len: 4 reflect Align len and field Align len: 8 8 case_test.go:196: (*te3).Cloud: HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company *te3.Cloud HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te3 size: 16 te3 Alignof: 8 te3 value len: 105 reflect Align len and field Align len: 8 8 case_test.go: 結構體1字段 Cloud:HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te2:0xc0000621e0 case_test.go:198: 結構體2字段 Cloud:ali2 te2:0xc000062280 case_test.go:199: 結構體3字段 Cloud:HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te3:0xc0000621e0
小結
我們介紹了unsafe包的檢查功能,在初始化時,go結構體已經分配了對於的內存空間,
一個結構體而言,結構體屬性爲隨機順序的,go將分配更多內存空間。 即使是複製後。
比如 結構體的Cloud 字段。
Sizeof表達式大小總是16,
而對齊係數 Alignof 大小總是8,
而在不同的結構體實例中值長度可以爲 4,13, 105.
本節源碼地址:
https://github.com/hahamx/examples/tree/main/alg_practice/2_sys_io