为什么 `[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
您可以使用以下方法验证结构的布局:
- unsafe.Alignof returns 要求的对齐方式
- unsafe.Offsetof 计算字段相对于其包含孔的封闭结构开始的偏移量
[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 .. 0x103
。 bar2.A
的地址将是 0x104
,它在结构的内存之外。对于此结构的数组(例如 x [5]bar2
),如果数组从 0x100
开始,x[0]
的地址将为 0x100
,x[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
参见相关问题:
[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
您可以使用以下方法验证结构的布局:
- unsafe.Alignof returns 要求的对齐方式
- unsafe.Offsetof 计算字段相对于其包含孔的封闭结构开始的偏移量
[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 .. 0x103
。 bar2.A
的地址将是 0x104
,它在结构的内存之外。对于此结构的数组(例如 x [5]bar2
),如果数组从 0x100
开始,x[0]
的地址将为 0x100
,x[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 valuesunsafe.Alignof(x.f)
for each fieldf
ofx
, but at least1
.
如果是这样,添加一个额外的 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
参见相关问题: