如果字段顺序不同,结构体的大小也会不同。

huangapple go评论67阅读模式
英文:

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{}))
}
  1. 结构体 AB 具有相同的字段,但如果以不同的顺序指定,它们的大小会有所不同。为什么会这样?

  2. 结构体 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{}))
}
  1. Struct A and B have the same fields, but if specified in different order they result in different size. why?

  2. Size of struct C is zero. How much memory is allocated by the system for a := 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的倍数,因为这是其所有字段中的最大大小。参见:规范:大小对齐保证:

对于结构类型的变量xunsafe.Alignof(x)是对于x的每个字段f的所有值unsafe.Alignof(x.f)中的最大值,但至少为1

B的情况下(如果GOARCH=386,这是你的情况),在类型为boolB.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

huangapple
  • 本文由 发表于 2015年12月11日 16:40:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/34219232.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定