Fundamentals 12 min read

Understanding Go Memory Alignment: A Complete Guide

This article explains Go’s type size specifications and alignment guarantees, demonstrates how to compute a struct’s memory footprint using unsafe.Sizeof and unsafe.Alignof, walks through concrete examples with structs T and R, and shows how field ordering affects actual memory usage.

Nullbody Notes
Nullbody Notes
Nullbody Notes
Understanding Go Memory Alignment: A Complete Guide

Type Sizes

According to the Go language specification, the basic numeric types have the following fixed sizes (in bytes):

type   size in bytes
byte, uint8, int8               1
uint16, int16                    2
uint32, int32, float32           4
uint64, int64, float64, complex64 8
complex128                       16
uint, int        either 32 or 64 bits depending on the target architecture
uintptr          an unsigned integer large enough to hold a pointer value

On a 64‑bit system, uint and int are 8 bytes; on a 32‑bit system they are 4 bytes.

Getting Type Sizes in Code

func main() {
    var i bool = true
    var j int = 40
    fmt.Println("bool-size", unsafe.Sizeof(i))
    fmt.Println("int-size", unsafe.Sizeof(j))
}

Running the program on a 64‑bit machine prints:

bool-size 1
int-size 8

Alignment Guarantees

Alignment guarantee (also called value‑address alignment) means that a variable’s start address is always a multiple of its alignment value.

The Go spec defines the minimal alignment properties as:

1. For any variable x, unsafe.Alignof(x) ≥ 1.
2. For a struct variable x, unsafe.Alignof(x) is the largest unsafe.Alignof of its fields (minimum 1).
3. For an array variable x, unsafe.Alignof(x) equals the alignment of the array’s element type.

Getting Alignment in Code

func main() {
    var i bool = true
    var j int = 40
    fmt.Println("bool-align", unsafe.Alignof(i)) // general alignment
    fmt.Println("int-align", unsafe.Alignof(j))

    type temp struct {
        a bool
        b int
    }
    var tmp = temp{}
    fmt.Println("a", unsafe.Alignof(tmp.a)) // field alignment
    fmt.Println("b", unsafe.Alignof(tmp.b))
}

Output on a 64‑bit system:

bool-align 1
int-align 8
a 1
b 8

Practical Example – Struct T

type T struct {
    a bool
    b int8
    c uint16
    d uint32
    e int64
    f bool
}

func main() {
    var t = T{}
    fmt.Println("size", unsafe.Sizeof(t), "align", unsafe.Alignof(t))
    fmt.Println("a", unsafe.Sizeof(t.a), "align", unsafe.Alignof(t.a), "offset", unsafe.Offsetof(t.a))
    fmt.Println("b", unsafe.Sizeof(t.b), "align", unsafe.Alignof(t.b), "offset", unsafe.Offsetof(t.b))
    fmt.Println("c", unsafe.Sizeof(t.c), "align", unsafe.Alignof(t.c), "offset", unsafe.Offsetof(t.c))
    fmt.Println("d", unsafe.Sizeof(t.d), "align", unsafe.Alignof(t.d), "offset", unsafe.Offsetof(t.d))
    fmt.Println("e", unsafe.Sizeof(t.e), "align", unsafe.Alignof(t.e), "offset", unsafe.Offsetof(t.e))
    fmt.Println("f", unsafe.Sizeof(t.f), "align", unsafe.Alignof(t.f), "offset", unsafe.Offsetof(t.f))
}

Running the program yields:

size 24 align 8
 a 1 align 1 offset 0
 b 1 align 1 offset 1
 c 2 align 2 offset 2
 d 4 align 4 offset 4
 e 8 align 8 offset 8
 f 1 align 1 offset 16

Summing the field sizes (1+1+2+4+8+1) gives 17 bytes, which is smaller than the reported struct size of 24 bytes. The discrepancy is caused by two padding rules:

Each field’s start address must be a multiple of its alignment.

The total size of the struct must be a multiple of the struct’s own alignment (the maximum field alignment, here 8 bytes).

After placing the fields according to their alignment, 7 bytes of padding are inserted after field f to bring the total size to the next multiple of 8, resulting in 24 bytes.

Effect of Field Ordering – Struct R

Reordering the fields (placing the larger uint32 before the uint16) produces a different layout:

type R struct {
    a bool
    b int8
    d uint32
    c uint16
    e int64
    f bool
}

Running the same inspection code on R prints:

size 32 align 8
 a 1 align 1 offset 0
 b 1 align 1 offset 1
 d 4 align 4 offset 4
 c 2 align 2 offset 8
 e 8 align 8 offset 16
 f 1 align 1 offset 24

Because the 4‑byte field d is placed before the 2‑byte field c, the compiler inserts additional padding to keep each field aligned, resulting in a total size of 32 bytes instead of 24.

Placing larger fields after smaller ones can increase the overall memory consumption of a struct.

Conclusion

Inspecting unsafe.Sizeof, unsafe.Alignof, and unsafe.Offsetof shows that Go’s type size table and alignment guarantees determine the actual memory layout of structs. Field ordering influences padding and total size. In most everyday code the compiler handles alignment automatically, but when memory usage is critical, arranging fields from largest to smallest can reduce wasted space.

Source code repository: https://github.com/gofish2020/zeus/tree/main/source/memoryalign

References:

https://golang.google.cn/ref/spec#Size_and_alignment_guarantees

https://golang.google.cn/ref/spec#Numeric_types

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Gomemory alignmentunsafesizeofstruct layoutalignof
Nullbody Notes
Written by

Nullbody Notes

Go backend development, learning open-source project source code together, focusing on simplicity and practicality.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.