1。结构尺寸
TL;DR; (总结):如果您对字段重新排序,将使用不同的隐式填充,并且隐式填充计入struct 的大小。
请注意,结果取决于目标架构;您发布的结果适用于 GOARCH=386,但在 GOARCH=amd64 时,A{} 和 B{} 的大小均为 24 字节。
struct的字段地址必须对齐,int64类型的字段地址必须是8字节的倍数。 Spec: Package unsafe:
计算机架构可能要求内存地址对齐;也就是说,对于一个变量的地址是一个因子的倍数,变量的类型的对齐。函数Alignof 接受一个表示任何类型变量的表达式,并以字节为单位返回变量(类型)的对齐方式。
int64 的对齐是 8 个字节:
fmt.Println(unsafe.Alignof((int64(0)))) // Prints 8
所以在A 的情况下,因为第一个字段是bool,在A.a 之后有一个7 字节的隐式填充,因此A.b 类型为int64 可以从一个地址为8 的倍数。因为struct 本身与 8 的倍数对齐,所以保证了这一点(确切需要 7 字节填充),因为这是其所有字段的最大大小。见:Spec: Size alignment guarantees:
对于结构类型的变量x:unsafe.Alignof(x) 是x 中每个字段f 的所有值unsafe.Alignof(x.f) 中的最大值,但至少是1。
在B 的情况下(如果GOARCH=386 是你的情况)在B.a 类型为bool 的字段之后将只有一个3 字节的隐式填充,因为该字段后面是一个字段输入int(大小为4字节)而不是int64。
如果GOARCH=386,int 的对齐为 4 个字节,如果GOARCH=amd64,则为 8 个字节:
fmt.Println(unsafe.Alignof((int(0)))) // Prints 4 if GOARCH=386, and 8 if GOARCH=amd64
使用unsafe.Offsetof()查找字段的偏移量:
// output 24
a := A{}
fmt.Println(unsafe.Sizeof(a),
unsafe.Offsetof(a.a), unsafe.Offsetof(a.b), unsafe.Offsetof(a.c))
// output 16
b := B{}
fmt.Println(unsafe.Sizeof(b),
unsafe.Offsetof(b.b), unsafe.Offsetof(b.a), unsafe.Offsetof(b.c))
// output 0
fmt.Println(unsafe.Sizeof(C{}))
var i int
fmt.Println(unsafe.Sizeof(i))
如果GOARCH=386 则输出(在Go Playground 上尝试):
24 0 8 16
16 0 8 12
0
4
如果GOARCH=amd64:则输出:
24 0 8 16
24 0 8 16
0
8
2。零大小值
Spec: Size alignment guarantees:
如果结构或数组类型不包含大小大于零的字段(或元素),则它的大小为零。 两个不同的零大小变量在内存中可能具有相同的地址。
所以规范只是提示使用相同的内存地址,但这不是必需的。但是当前的实现遵循它。也就是说,不会为大小为零的类型的值分配内存,这包括空结构 struct{} 和长度为零的数组,例如[0]int,或元素大小为零(且长度任意)的数组。
看这个例子:
a := struct{}{}
b := struct{}{}
c := [0]int{}
d := [3]struct{}{}
fmt.Printf("%p %p %p %p %p", &a, &b, &c, &d, &d[2])
输出(在Go Playground上试试):所有地址都一样。
0x21cd7c 0x21cd7c 0x21cd7c 0x21cd7c 0x21cd7c
有关有趣且相关的主题,请阅读:Dave Cheney: Padding is hard