【问题标题】:Why position of `[0]byte` in the struct matters?为什么结构中 `[0]byte` 的位置很重要?
【发布时间】:2017-07-12 03:46:13
【问题描述】:

[0]byte 在 golang 中不应该占用任何内存空间。但这两个结构的大小不同。

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

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

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

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

【问题讨论】:

  • 尺寸是多少?
  • bar2:8 大小,bar3:4 大小

标签: memory go struct padding sizeof


【解决方案1】:

这是由于棘手的填充造成的。

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

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 中不为第一个字段(bar1.A)保留任何空间并将bar1.I 字段布局为 0 是完全有效的偏移量。

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

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

bar2 的情况下,第二个字段不能有一个与第一个字段重叠的地址(因此是偏移量),因此它的偏移量不能小于int 的大小,如果是 4 个字节32 位架构(在 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 但这是另一个结构值的地址!不酷。

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

对于结构类型的变量xunsafe.Alignof(x)x 中每个字段f 的所有值unsafe.Alignof(x.f) 中的最大值,但至少是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 个字节)(在 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

查看相关问题:Struct has different size if the field order is different

【讨论】:

    【解决方案2】:

    原因是“坑”:

    空洞是编译器添加的未使用的空间,以确保 以下字段或元素相对于开始正确对齐 结构或数组的 [1]

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

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

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

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

    【讨论】:

    • bool使用内存空间,与[0]byte不同。如果在 bar2 和 bar3 中将 [0]byte 替换为 bool,则两个结构将具有相同的大小。这里有一个完整的例子:play.golang.org/p/hNcG9gRlQx
    • 是的,但是 [0]byte 所需的对齐方式是 1。您可以使用 unsafe.Alignof 检查所需的对齐方式,并使用 unsafe.Offsetof 计算字段的偏移量。见play.golang.org/p/aEEqDBdBZW
    • 如果打印bool[0]byte成员中的alignofoffset,你会发现它们对于两种不同的类型是相同的。所以,这不能解释结构大小的差异。见play.golang.org/p/oAnH2XodPa
    猜你喜欢
    • 2015-09-07
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-01
    相关资源
    最近更新 更多