c 的字節對齊很重要,由於c 支持指針運算。在golang 裏面通常是慎用指針運算的,因此,這部分不多用,可是有些場景爲了性能不得不用到指針運算,這個時候,知道golang 的內存分配就很重要了。可是基本不多有相關的參考資料,不少也不靠譜,這裏借鑑c 的規則驗證golang 的內存對齊規則。golang
該文章後續仍在不斷的更新修改中, 請移步到原文地址http://www.dmwan.cc/?p=154數組
首先,有個問題,爲何 函數 unsafe.Offsetof(A.a1) 的參數怪怪的,非得把結構體類型也傳進去?函數
c中字節對齊規則:
一、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,之後每一個數據成員的地址必須是它自己大小或對齊參數二者中較小的一個的倍數。
二、總體對齊規則:在數據成員完成各自對齊以後,結構(或聯合)自己也要進行對齊,總體長度必須是對齊參數和結構體最長的元素長度中較小的一個的倍數。性能
參考規則咱們看下面這些結構的數據分配,注:64位平臺,對齊參數8.指針
package main import ( "fmt" "unsafe" ) type A struct { a1 int8 // offset = 0 a2 int16 // offset = 1 / min(8, sizeof(int16)=2 )=2 } // 4 / min(8, 2) 大小爲4 type B struct { b1 int16 // offset = 0 b2 int8 // offset = 2 / min(8, 1) = 2 b3 int // offset = 3 / min(8, 1) = 3 } // [0, 1] + 2 + [3, 10]=11 / min(8, 8) 不盡,不符合規則2,大小爲16,內存圓整 type C struct { c1 int8 // offset = 0 c2 float32 // offset = 1 /min(8, 4) = 4 c3 int // offset = 8 / min(8, 8)=8 } // 16/ min(8, 8) 大小爲16 type D struct { d1 float32 // offset = 0 d2 int // offset = 4 / min(8, 8) = 8 d3 int8 // offset = 16 / min(8, 1) = 16 } // 17 /min(8, 8), 大小爲17,數據圓整,Padding 爲24 /8 = 3能整除 func main() { var a = A{} var b = B{} var c = C{} var d = D{} // size of A = 4 // a1: 1 字節 + 1 字節padding // a2: 2 字節 fmt.Printf("a.a1 offset %v \n", unsafe.Offsetof(a.a1)) fmt.Printf("a.a2 offset %v \n", unsafe.Offsetof(a.a2)) fmt.Printf("size of A = %d\n", unsafe.Sizeof(a)) // size of B = 16 // b1: 2 字節 // b2: 1 字節 + 5 字節padding // b3: 8 字節 fmt.Printf("b.b1 offset %v \n", unsafe.Offsetof(b.b1)) fmt.Printf("b.b2 offset %v \n", unsafe.Offsetof(b.b2)) fmt.Printf("b.b3 offset %v \n", unsafe.Offsetof(b.b3)) fmt.Printf("size of B = %d\n", unsafe.Sizeof(b)) // size of c = 16 // c1: 1 字節 + 3 字節padding // c2: 4 字節 // c3: 8 字節 fmt.Printf("c.c1 offset %v \n", unsafe.Offsetof(c.c1)) fmt.Printf("c.c2 offset %v \n", unsafe.Offsetof(c.c2)) fmt.Printf("c.c3 offset %v \n", unsafe.Offsetof(c.c3)) fmt.Printf("size of C = %d\n", unsafe.Sizeof(c)) // size of d = 24 // d1: 4字節 + 4字節padding // d2: 8 字節 // d3: 1字節 + 7 字節padding // d1的尾部padding的緣由是要保證是結構體自身也是對齊的 // 由於這樣能夠確保實現結構體數組時候裏面每一個元素也是對齊的 fmt.Printf("d.d1 offset %v \n", unsafe.Offsetof(d.d1)) fmt.Printf("d.d2 offset %v \n", unsafe.Offsetof(d.d2)) fmt.Printf("d.d3 offset %v \n", unsafe.Offsetof(d.d3)) fmt.Printf("size of D = %d\n", unsafe.Sizeof(d)) // 因爲有補齊,兩個結構體即使有相同類型的字段,但先後順序不一樣也可致使size不一樣 }
發現c 的內存分配規則其實和golang 是一致的。最後其實這裏是否是一致對於使用來講不會有太大影響,由於指針計算,轉換以前,會提早用offsetof 計算,這個偏移,golang 會將padding 都算進去, 和c 用的時候不太同樣,這樣作,不會出錯。code
這裏特別提下就是不一樣平臺和對齊參數的不一致,會致使結果徹底不一樣。內存