Why is the position of `[0] byte` in the structure important?
[0]byte
in golang shouldn't take up memory. But these two structures are of different sizes.
type bar2 struct {
A int
_ [0]byte
}
type bar3 struct {
_ [0]byte
A int
}
So why is position taking place here [0]byte
?
By the way, I am using a method unsafe.Sizeof()
to check the size of the structure. See full example .
source to share
This is due to a complex addition.
First, let me rename structs and fields a little to make it easier to talk about them:
type bar1 struct {
A [0]byte
I int
}
type bar2 struct {
I int
A [0]byte
}
This of course doesn't change the size and offsets, as can be checked on the Go Playground :
bar1 size: 4
bar1.A offset: 0
bar1.I offset: 0
bar2 size: 8
bar2.I offset: 0
bar2.A offset: 4
The size of the type value [0]byte
is zero, so it is bar1
completely impossible to reserve any space for the first field ( bar1.A
) and lay out the field bar1.I
with 0 offset.
The question is, why can't the compiler do the same in the second case (c bar2
)?
The field must have an address, which must be after the memory area reserved for the previous field. In the first case, the first field bar1.A
has a size of 0, so the second field can have an offset of 0, it will not "overlap" with the first field.
In the case bar2
, the second field cannot have an address (and therefore an offset) that overlaps with the first field, so its offset cannot be less than the size int
, which is 4 bytes in the case of 32-bit architectures (and 8 bytes in the case of 64- bit arch).
It still looks fine. But since it bar2.A
has a size of zero, why bar2
can't the size of the struct be 4 bytes (or 8 in a 64-bit architecture)?
This is because it is perfectly valid for addresses (and variables) that have size 0 . So what?
In case, the bar2
compiler must insert 4 (or 8) bytes' complement, otherwise the received field address bar2.A
will point outside the memory area reserved for the value of the type bar2
.
As an example, without padding, the value bar2
could have an address 0x100
, size 4, so the memory reserved for the struct value has a range of addresses 0x100 .. 0x103
. The address bar2.A
will be 0x104
outside the memory of the structure. In the case of an array of this structure (for example x [5]bar2
), if the array starts with 0x100
, the address x[0]
will be 0x100
, the address x[0].A
will be 0x104
, and the address of the subsequent element x[1]
will also be 0x104
, but the address of a different structure value! Not cool.
To avoid this, the compiler inserts indentation (which will be 4 or 8 bytes depending on the arch) so that accessing the address bar2.A
will not result in the address being outside the memory of the structure, which could otherwise raise questions and cause problems with garbage collection (for example, if only the address is stored bar2.A
, but not the structure or another pointer to it or its other fields, the entire structure should not be garbage collected, but since no pointer points to its memory area, this would seem to be valid for this). The nested padding will be 4 (or 8) bytes, because Spec: size and alignment guarantees:
For a variable of
x
type struct:unsafe.Alignof(x)
is the largest of all valuesunsafe.Alignof(x.f)
for each fieldf
ofx
, but not less1
.
If so, adding an extra margin int
will make both structures equal in size:
type bar1 struct {
I int
A [0]byte
X int
}
type bar2 struct {
A [0]byte
I int
X int
}
Indeed, they both have 8 bytes on a 32-bit architecture (and 16 bytes on a 64-bit architecture) (try it on the Go Playground ):
bar1 size: 8
bar1.I offset: 0
bar1.A offset: 4
bar1.X offset: 4
bar2 size: 8
bar2.A offset: 0
bar2.I offset: 0
bar2.X offset: 4
See related question: Structure has a different size if the field order is different from
source to share
The reason - "holes":
Holes are unused spaces added by the compiler to ensure that the next field or element is correctly aligned relative to the start of a structure or array [1]
For example (numbers are based on whatever hardware the playground uses to play):
struct {bool; float64; int16} // 24 bytes
struct {float64; bool; int16} // 16 bytes
You can check the layout of the structure using:
- unsafe.Alignof returns the required alignment
- unsafe.Offsetof calculates the offset of the field relative to the start of its inclusion structure, including holes
[1] p354 Donovan, Kernighan, AD, BK, 2016. The GO Programming Language. 1st ed. New York: Addison-Wesley.
source to share