这是由于一个tricky填充。
首先,请允许我稍微重命名结构和字段,以便更容易讨论它们:
type bar1 struct {
A [0]byte
I int
}
type bar2 struct {
I int
A [0]byte
}
这当然不会改变大小和偏移量,可以在去游乐场 https://play.golang.org/p/NcTeFpDrJi:
bar1 size: 4
bar1.A offset: 0
bar1.I offset: 0
bar2 size: 8
bar2.I offset: 0
bar2.A offset: 4
类型值的大小[0]byte
为零,因此它完全有效bar1
不为第一个字段保留任何空间(bar1.A
),并布置bar1.I
偏移量为 0 的字段。
问题是:为什么编译器不能在第二种情况下做同样的事情(使用bar2
)?
字段必须有一个地址,该地址必须位于为前一个字段保留的存储区域之后。在第一种情况下,第一个字段bar1.A
大小为 0,因此第二个字段的偏移量可能为 0,它不会与第一个字段“重叠”。
的情况下bar2
,第二个字段不能有与第一个字段重叠的地址(因此也有偏移量),因此它的偏移量不能小于int
在 32 位架构中为 4 个字节(在 64 位架构中为 8 个字节)。
这看起来还是可以的。但是由于bar2.A
大小为零,为什么结构体的大小不能bar2
就是这样:4 个字节(或 64 位架构中的 8 个字节)?
这是因为它是完美的有效获取大小为 0 的字段(和变量)的地址。好吧,那又怎么样?
的情况下bar2
,编译器必须插入 4(或 8)字节填充,否则采用 a 的地址bar2.A
字段会指向内存区域之外为类型值保留bar2
.
举个例子,无填充值为bar2
可能有一个地址0x100
,大小为 4,因此为结构体值保留的内存具有地址范围0x100 .. 0x103
。地址bar2.A
将会0x104
,即在结构体内存之外。如果是此结构的数组(例如x [5]bar2
),如果数组开始于0x100
,地址x[0]
将会0x100
,地址x[0].A
将会0x104
,以及后续元素的地址x[1]
也会是0x104
但这是另一个结构体值的地址!不酷。
为了避免这种情况,编译器插入一个填充(根据架构,该填充将是 4 或 8 字节),以便获取bar2.A
不会导致地址位于结构体内存之外,否则可能会引发问题并导致垃圾收集方面的问题(例如,如果只有bar2.A
被保留,但不保留结构或指向它或其其他字段的另一个指针,整个结构不应被垃圾收集,但由于没有指针指向其内存区域,因此这样做似乎是有效的)。插入的填充将为 4(或 8)字节,因为规格:尺寸和对齐保证: https://golang.org/ref/spec#Size_and_alignment_guarantees
对于一个变量x
结构类型:unsafe.Alignof(x)
是所有值中最大的unsafe.Alignof(x.f)
对于每个字段f
of x
, 但至少1
.
如果是这样,添加一个额外的int
字段将使两个结构的大小相等:
type bar1 struct {
I int
A [0]byte
X int
}
type bar2 struct {
A [0]byte
I int
X int
}
事实上,它们在 32 位架构上都有 8 个字节(在 64 位架构上都有 16 个字节)(在去游乐场 https://play.golang.org/p/SBweaAzE2b):
bar1 size: 8
bar1.I offset: 0
bar1.A offset: 4
bar1.X offset: 4
bar2 size: 8
bar2.A offset: 0
bar2.I offset: 0
bar2.X offset: 4
查看相关问题:如果字段顺序不同,结构体的大小也不同 https://stackoverflow.com/questions/34219232/struct-has-different-size-if-the-field-order-is-different/34219916#34219916