【问题标题】:Struct has different size if the field order is different如果字段顺序不同,则结构具有不同的大小
【发布时间】:2016-03-17 02:41:31
【问题描述】:
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. 结构 AB 具有相同的字段,但如果以不同的顺序指定,它们会导致不同的大小。为什么?

  2. 结构C 的大小为零。系统为a := C{}分配了多少内存?

谢谢。

【问题讨论】:

  • 2.是否需要为元数据分配一些内存,例如类型信息。
  • 结构不包含类型信息(并且除了字段之外没有大小开销),这一切都由编译器处理。然而,接口包含类型和值。

标签: memory struct go padding sizeof


【解决方案1】:

1。结构尺寸

TL;DR; (总结):如果您对字段重新排序,将使用不同的隐式填充,并且隐式填充计入struct 的大小。

请注意,结果取决于目标架构;您发布的结果适用于 GOARCH=386,但在 GOARCH=amd64 时,A{}B{} 的大小均为 24 字节。

struct的字段地址必须对齐,int64类型的字段地址必须是8字节的倍数。 Spec: Package unsafe:

计算机架构可能要求内存地址对齐;也就是说,对于一个变量的地址是一个因子的倍数,变量的类型的对齐。函数Alignof 接受一个表示任何类型变量的表达式,并以字节为单位返回变量(类型)的对齐方式。

int64 的对齐是 8 个字节:

fmt.Println(unsafe.Alignof((int64(0)))) // Prints 8

所以在A 的情况下,因为第一个字段是bool,在A.a 之后有一个7 字节的隐式填充,因此A.b 类型为int64 可以从一个地址为8 的倍数。因为struct 本身与 8 的倍数对齐,所以保证了这一点(确切需要 7 字节填充),因为这是其所有字段的最大大小。见:Spec: Size alignment guarantees:

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

B 的情况下(如果GOARCH=386 是你的情况)在B.a 类型为bool 的字段之后将只有一个3 字节的隐式填充,因为该字段后面是一个字段输入int(大小为4字节)而不是int64

如果GOARCH=386int 的对齐为 4 个字节,如果GOARCH=amd64,则为 8 个字节:

fmt.Println(unsafe.Alignof((int(0))))   // Prints 4 if GOARCH=386, and 8 if GOARCH=amd64

使用unsafe.Offsetof()查找字段的偏移量:

// 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))

如果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。零大小值

Spec: Size alignment guarantees:

如果结构或数组类型不包含大小大于零的字段(或元素),则它的大小为零。 两个不同的零大小变量在内存中可能具有相同的地址。

所以规范只是提示使用相同的内存地址,但这不是必需的。但是当前的实现遵循它。也就是说,不会为大小为零的类型的值分配内存,这包括空结构 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-05
    • 2017-07-15
    • 1970-01-01
    • 2011-06-26
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 1970-01-01
    相关资源
    最近更新 更多