英文:
Struct has different size if the field order is different
问题
以下是您提供的代码的翻译:
package main
import (
"fmt"
"unsafe"
)
type A struct {
a bool
b int64
c int
}
type B struct {
b int64
a bool
c int
}
type C struct {
}
func main() {
// 输出 24
fmt.Println(unsafe.Sizeof(A{}))
// 输出 16
fmt.Println(unsafe.Sizeof(B{}))
// 输出 0
fmt.Println(unsafe.Sizeof(C{}))
}
-
结构体
A
和B
具有相同的字段,但如果以不同的顺序指定,它们的大小会有所不同。为什么会这样? -
结构体
C
的大小为零。系统为a := C{}
分配了多少内存?
谢谢。
英文:
package main
import (
"fmt"
"unsafe"
)
type A struct {
a bool
b int64
c int
}
type B struct {
b int64
a bool
c int
}
type C struct {
}
func main() {
// output 24
fmt.Println(unsafe.Sizeof(A{}))
// output 16
fmt.Println(unsafe.Sizeof(B{}))
// output 0
fmt.Println(unsafe.Sizeof(C{}))
}
-
Struct
A
andB
have the same fields, but if specified in different order they result in different size. why? -
Size of struct
C
is zero. How much memory is allocated by the system fora := C{}
?
Thanks.
答案1
得分: 16
1. 结构体大小
TL;DR;(摘要):如果重新排序字段,则会使用不同的隐式填充,并且隐式填充计入struct
的大小。
请注意,结果取决于目标架构;你发布的结果适用于GOARCH=386
,但当GOARCH=amd64
时,A{}
和B{}
的大小都将为24字节。
结构体字段的地址必须对齐,并且int64
类型的字段的地址必须是8字节的倍数。规范:unsafe
包:
计算机架构可能要求内存地址对齐;也就是说,为了使变量的地址成为因子的倍数,变量的类型的对齐方式。函数
Alignof
接受一个表示任何类型的变量的表达式,并返回变量(类型的)对齐方式,以字节为单位。
int64
的对齐方式为8字节:
fmt.Println(unsafe.Alignof((int64(0)))) // 输出 8
因此,在A
的情况下,由于第一个字段是bool
,所以在A.a
之后有7字节的隐式填充,以便A.b
(类型为int64
)可以从地址开始,该地址是8的倍数。这(确切地需要7字节填充)是有保证的,因为struct
本身对齐到一个地址,该地址是8的倍数,因为这是其所有字段中的最大大小。参见:规范:大小对齐保证:
对于结构类型的变量
x
:unsafe.Alignof(x)
是对于x
的每个字段f
的所有值unsafe.Alignof(x.f)
中的最大值,但至少为1
。
在B
的情况下(如果GOARCH=386
,这是你的情况),在类型为bool
的B.a
字段之后只会有3字节的隐式填充,因为该字段后面是一个类型为int
(大小为4字节)而不是int64
的字段。
如果GOARCH=386
,则int
的对齐方式为4字节,如果GOARCH=amd64
,则为8字节:
fmt.Println(unsafe.Alignof((int(0)))) // 如果GOARCH=386,则输出 4,如果GOARCH=amd64,则输出 8
使用unsafe.Offsetof()
查找字段的偏移量:
// 输出 24
a := A{}
fmt.Println(unsafe.Sizeof(a),
unsafe.Offsetof(a.a), unsafe.Offsetof(a.b), unsafe.Offsetof(a.c))
// 输出 16
b := B{}
fmt.Println(unsafe.Sizeof(b),
unsafe.Offsetof(b.b), unsafe.Offsetof(b.a), unsafe.Offsetof(b.c))
// 输出 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. 零大小值
如果结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量在内存中可能具有相同的地址。
因此,规范只是给出了在相同的内存地址上使用相同的提示,但这不是一个要求。但是,当前的实现遵循这一点。也就是说,不会为大小为零的类型的值分配内存,这包括空结构体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
英文:
1. Struct sizes
TL;DR; (Summary): Different implicit padding will be used if you reorder the fields, and the implicit padding counts towards the size of the struct
.
Note that the result depends on the target architecture; results you posted applies when GOARCH=386
, but when GOARCH=amd64
, sizes of both A{}
and B{}
will be 24 bytes.
Address of fields of a struct must be aligned, and the address of fields of type int64
must be a multiple of 8 bytes. Spec: Package unsafe
:
> Computer architectures may require memory addresses to be aligned; that is, for addresses of a variable to be a multiple of a factor, the variable's type's alignment. The function Alignof
takes an expression denoting a variable of any type and returns the alignment of the (type of the) variable in bytes.
Align of int64
is 8 bytes:
fmt.Println(unsafe.Alignof((int64(0)))) // Prints 8
So in case of A
since first field is bool
, there is a 7-byte implicit padding after A.a
so that A.b
which is of type int64
can start on an address that is a multiple of 8. This (that 7-byte padding is needed exactly) is guaranteed as the struct
itself is aligned to an address which is a multiple of 8, because that is the largest size of all of its fields. See: Spec: Size 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
.
In case of B
(and if GOARCH=386
which is your case) there will only be a 3-byte implicit padding after the B.a
field of type bool
because this field is followed by a field of type int
(which has size of 4 bytes) and not int64
.
Align of int
is 4 bytes if GOARCH=386
, and 8 bytes if GOARCH=amd64
:
fmt.Println(unsafe.Alignof((int(0)))) // Prints 4 if GOARCH=386, and 8 if GOARCH=amd64
Use unsafe.Offsetof()
to find out the offsets of fields:
// 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))
Output if GOARCH=386
(try it on the Go Playground):
24 0 8 16
16 0 8 12
0
4
Output if GOARCH=amd64
:
24 0 8 16
24 0 8 16
0
8
2. Zero size values
Spec: Size alignment guarantees:
> A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.
So the spec just gives a hint to use the same memory address but it's not a requirement. But current implementations follow it. That is, no memory will be allocated for values of types having a size of zero, this includes the empty struct struct{}
and arrays of zero length, e.g. [0]int
, or arrays whose elements has a size of zero (and with arbitrary length).
See this example:
a := struct{}{}
b := struct{}{}
c := [0]int{}
d := [3]struct{}{}
fmt.Printf("%p %p %p %p %p", &a, &b, &c, &d, &d[2])
Output (try it on the Go Playground): All the addresses are the same.
0x21cd7c 0x21cd7c 0x21cd7c 0x21cd7c 0x21cd7c
For an interesting and related topic, read: Dave Cheney: Padding is hard
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论