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
这里特别提下就是不一样平台和对齐参数的不一致,会致使结果彻底不一样。内存