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 struc
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 valuesunsafe.Alignof(x.f)
for each fieldf
ofx
, but at least1
.
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
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