为什么 `[0]byte` 在结构中的位置很重要?

Why position of `[0]byte` in the struct matters?

golang中的

[0]byte不应该占用任何内存space。但是这两个结构有不同的大小。

type bar2 struct {
    A int
    _ [0]byte
}

type bar3 struct {
    _ [0]byte
    A int   
}

那么为什么 [0]byte 的位置在这里很重要?

顺便说一下,我使用 unsafe.Sizeof() 方法来检查结构大小。请参阅 full example .

原因是"holes":

Holes are the unused spaces added by the compiler to ensure that the following field or element is properly aligned relative to the start of the struct or array [1]

例如(数字基于 go playground 使用的任何硬件):

struct {bool; float64; int16} // 24 bytes
struct {float64; bool; int16} // 16 bytes

您可以使用以下方法验证结构的布局:

[1] p354 Donovan, Kernighan, AD, BK, 2016。GO 编程语言。第一版。纽约:Addison-Wesley。

这是由于 棘手的 填充。

首先,请允许我稍微重命名结构和字段,以便更轻松地讨论它们:

type bar1 struct {
    A [0]byte
    I int
}

type bar2 struct {
    I int
    A [0]byte
}

这当然不会改变大小和偏移量,因为可以在 Go Playground:

上验证
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 中完全有效不为第一个字段保留任何 space (bar1.A ), 并用 0 偏移布置 bar1.I 字段。

问题是:为什么编译器不能在第二种情况下(bar2)做同样的事情?

字段必须有一个地址,该地址必须在为前一个字段保留的内存区域之后。在第一种情况下,第一个字段 bar1.A 的大小为 0,因此第二个字段的偏移量可能为 0,它不会 "overlap" 第一个字段。

bar2的情况下,第二个字段不能有与第一个字段重叠的地址(因此偏移量),因此它的偏移量不能小于int的大小在 32 位架构的情况下是 4 个字节(在 64 位架构的情况下是 8 个字节)。

这看起来还可以。但是由于 bar2.A 的大小为零,为什么结构 bar2 的大小不能是:4 个字节(或 64 位架构中的 8 个字节)?

这是因为获取大小为 0 的字段(和变量)的地址是完全有效的。好的,那又怎样?

bar2 的情况下,编译器必须插入一个 4(或 8)字节的填充,否则获取 bar2.A 字段的地址将指向 bar2.

类型的值保留的内存区域

例如,没有填充 bar2 的值可能有 0x100 的地址,大小为 4,因此为结构值保留的内存地址范围 0x100 .. 0x103bar2.A 的地址将是 0x104,它在结构的内存之外。对于此结构的数组(例如 x [5]bar2),如果数组从 0x100 开始,x[0] 的地址将为 0x100x[0].A 的地址将是 0x104,后续元素的地址 x[1] 也将是 0x104 但那是另一个结构值的地址!不酷。

为了避免这种情况,编译器插入了一个填充(根据 arch 的不同,这将是 4 或 8 个字节),因此采用 bar2.A 的地址不会导致地址位于结构的外部内存,否则可能会引发问题并导致有关垃圾回收的问题(例如,如果仅保留 bar2.A 的地址但不保留结构或指向它或其其他字段的另一个指针,则不应对整个结构进行垃圾回收,但因为没有指针指向它的内存区域,所以这样做似乎是有效的)。插入的填充将是 4(或 8)个字节,因为 Spec: Size and alignment guarantees:

For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe.Alignof(x.f) for each field f of x, but at least 1.

如果是这样,添加一个额外的 int 字段将使两个结构的大小相等:

type bar1 struct {
    I int
    A [0]byte
    X int
}

type bar2 struct {
    A [0]byte
    I int
    X int
}

确实,它们在 32 位 arch 上都有 8 个字节(在 64 位 arch 上有 16 个字节)(在 Go Playground 上试试):

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

参见相关问题: