Memory Alignment, Padding, and Data Oriented Design
Pointers, References and Memory Layout

3.5 Memory Alignment, Padding, and Data Oriented Design

You have seen pointers, stack versus heap, and lifetime rules. Now we connect those ideas to how the CPU actually loads data: alignment, padding, and layouts that keep cache lines full.

Note

Cache hierarchy in 60 seconds: CPU registers and L1 caches are fastest (roughly single-digit to low tens of cycles). L2/L3 are larger but slower. Main DRAM is orders of magnitude slower than L1. The CPU always moves data in cache lines (often 64 bytes on x86_64 and ARM). A cache miss to DRAM can cost hundreds of cycles, enough to hide many arithmetic instructions. Data layout in this lesson, container choice (Lesson 6.1), and false sharing (Lesson 9.6) all exist to keep useful bytes in the levels the core can reach quickly.

Let's look at custom alignment, false sharing foundations, and Data Oriented Design patterns.

Structure Padding and Member Reordering

To understand alignment, we must look at how the compiler lays out custom structures in memory. When you declare a structure, the compiler must ensure that every member variable satisfies its own alignment requirement. To achieve this, it inserts unused bytes of memory called padding between members.

Consider these two structures containing the exact same data types. The first structure is unaligned, forcing the compiler to pad it to 12 bytes; the second reorders members, shrinking the footprint to 8 bytes:

12 bytes total (4 bytes of padding inserted)
struct Unaligned {
    char a;   // 1 byte
    // 3 bytes of padding inserted here so b starts at a 4-byte boundary
    int b;    // 4 bytes
    char c;   // 1 byte
    // 3 bytes of trailing padding so next struct is aligned
};

// 8 bytes total (2 bytes of padding)
struct Aligned {
    int b;    // 4 bytes
    char a;   // 1 byte
    char c;   // 1 byte
    // 2 bytes of padding to align the overall struct size
};
Comparing structure padding layouts.

By sorting members from largest to smallest alignment requirements, we can dramatically shrink the memory footprint of our structures and fit more elements into CPU cache lines.

Interactive Memory Visualizer

Hardware Alignment & Structure Padding

Click toggle below to see how compiler reordering packs members into smaller cache line boundaries.

Byte 0
Byte 1
Byte 2
Byte 3
achar+0B
padpadding+1B
padpadding+2B
padpadding+3B
b [0]int+4B
b [1]int+5B
b [2]int+6B
b [3]int+7B
cchar+8B
padpadding+9B
padpadding+10B
padpadding+11B

Unaligned Layout: The compiler must align the 4-byte int b to a multiple of 4 bytes. Thus, 3 bytes of silent padding are added after char a. Another 3 bytes are added at the end so the entire struct is divisible by the largest member alignment. Total: 12 Bytes.

The alignof and alignas Keywords

Every data type has an alignment requirement (usually equal to its size). The CPU expects a 4 byte integer to reside at a memory address that is a multiple of 4. You can query a type's alignment using alignof.

Sometimes, you need to force a structure to align to a specific boundary (like a 64 byte cache line) for SIMD vectorization or multithreading performance. C++ provides the alignas specifier:

#include <iostream>

// Default alignment (usually 4 bytes for the int)
struct StandardData {
    int value;
};

// Force alignment to 64-byte boundaries (standard CPU cache line size)
struct alignas(64) CacheAlignedData {
    int value;
};

int main() {
    std::cout << "Standard alignment: " << alignof(StandardData) << " bytes\n"; // Prints 4
    std::cout << "Custom alignment: " << alignof(CacheAlignedData) << " bytes\n"; // Prints 64
    return 0;
}
Enforcing strict memory alignment using alignas.

SIMD Alignment and alignas(32)

AVX instructions process 256 bits (32 bytes) of data simultaneously. Lesson 9.8 covers SIMD auto-vectorization in depth; for now, know that CPUs prefer 32-byte-aligned addresses for the fastest loads, though unaligned access (vmovups) still works with a penalty.

By using alignas(32) on your arrays or structures, you tell the compiler the data is aligned. That enables fast vmovaps (aligned move) instructions instead of slower vmovups (unaligned move) on many targets.

Why Structure Layout Matters (AoS vs SoA)

When you create an array of objects, the standard C++ object oriented approach is an Array of Structs (AoS). You define an entity, then make an array of them.

While this is logically clean, it is physically disastrous for hardware performance if you only need to process one specific field across all entities. When the CPU loads an entity from RAM, it pulls a 64 byte chunk of adjacent data (a cache line). If you only want to update the position, you are wasting cache bandwidth loading velocity, color, and name.

Array of Structs (AoS) layout
struct Entity {
    float posX, posY;
    float velX, velY;
    int color;
    bool isActive;
};

// The CPU loads roughly 24–32 bytes per Entity (padding varies by platform),
// even if we only want to update posX!
std::vector<Entity> entities(1000);
Array of Structs (AoS) - Bad for hardware cache.

Data Oriented Design: Struct of Arrays (SoA)

To achieve maximum hardware performance (common in high-performance engines such as Unity DOTS and custom ECS designs), systems engineers use a Struct of Arrays (SoA) layout. You invert the structure, storing properties in separate, flat arrays:

Struct of Arrays (SoA) layout
struct Entities {
    std::vector<float> posX;
    std::vector<float> posY;
    std::vector<float> velX;
    std::vector<float> velY;
    std::vector<int> color;
    std::vector<unsigned char> isActive; // Avoid std::vector<bool>—it is a packed specialization
};

Entities registry;
Struct of Arrays (SoA) - Optimal for hardware cache.

With the SoA layout, if you write a loop to update all posX values, the CPU cache line will pull in exactly 16 posX floats at a time, completely filling the cache with useful data. Zero memory bandwidth is wasted on colors or velocities. This layout is also perfectly structured for SIMD auto-vectorization (Lesson 9.8).

* The Grocery Bag Metaphor: AoS is like packing each grocery bag with exactly one apple, one banana, and one carton of milk (representing one entity). If you just want to eat all the apples, you have to open every single bag. SoA is like packing all the apples into one giant bag, all bananas into another. If you want apples, you grab the apple bag and feast efficiently.

Finished reading this lesson?