如果字段顺序不同,则Struct具有不同的大小

lei*_*lin 3 memory struct sizeof padding go

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{}))
}
Run Code Online (Sandbox Code Playgroud)
  1. 结构AB具有相同的字段,但如果以不同的顺序指定,则会产生不同的大小.为什么?

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

谢谢.

icz*_*cza 12

1.结构尺寸

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

请注意,结果取决于目标体系结构; 适用结果,你贴的时候GOARCH=386,但是当GOARCH=amd64,二者的尺寸A{}B{}将是24个字节.

必须对齐结构的字段地址,并且类型字段的地址int64必须是8个字节的倍数.规格:包装unsafe:

计算机体系结构可能需要对齐存储器地址; 也就是说,对于变量的地址是因子的倍数,变量的类型的对齐.该函数Alignof采用表达任何类型变量的表达式,并以字节为单位返回(变量类型)的对齐方式.

对齐int64是8个字节:

fmt.Println(unsafe.Alignof((int64(0)))) // Prints 8
Run Code Online (Sandbox Code Playgroud)

因此,在A第一个字段bool出现的情况下,之后有一个7字节的隐式填充,A.a因此A.b类型int64可以从8的倍数的地址开始.这(确切地需要7字节填充)保证为它struct本身与一个8的倍数的地址对齐,因为它是所有字段的最大尺寸.请参阅:规格:尺寸对齐保证:

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

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

对齐int是4个字节if GOARCH=386和8个字节if GOARCH=amd64:

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

使用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))
Run Code Online (Sandbox Code Playgroud)

输出if GOARCH=386(在Go Playground上试试):

24 0 8 16
16 0 8 12
0
4
Run Code Online (Sandbox Code Playgroud)

输出如果GOARCH=amd64:

24 0 8 16
24 0 8 16
0
8
Run Code Online (Sandbox Code Playgroud)

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])
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上尝试):所有地址都相同.

0x21cd7c 0x21cd7c 0x21cd7c 0x21cd7c 0x21cd7c
Run Code Online (Sandbox Code Playgroud)

有关一个有趣且相关的主题,请阅读:Dave Cheney:Padding很难