CLOSE
Updated on 01 Jul, 202528 mins read 240 views

For systems programmers working close to the hardware, memory layout is a crucial factor in designing efficient and correct programs. Poor memory alignment leads to hard-to-find bugs, performance degradation, and sometimes outright crashes — especially in embedded or cross-platform contexts.

This article explores the core principles of memory alignment and padding in C++, explains why misalignment hurts, and shows how to optimize struct layout safely.

What Is Memory Alignment?

Memory alignment refers to placing data at memory addresses that are multiples of the data’s size or required alignment.

  • On a 32-bit system:
    • Data is typically accessed in 4-byte words
    • Types like int and float (4 bytes) must be stored at addresses divisible by 4
  • On a 64-bit system:
    • Many types (like double or pointers) align to 8 bytes

Why Alignment Matters

1. CPU Efficiency

Aligned access lets the CPU fetch data in a single operation. Unaligned access can cause:

  • Multiple memory fetches
  • Bit shifting and recombination
  • Stall cycles

2. Hardware Restrictions

Some architectures (like ARM, SPARC, MIPS) do not allow unaligned access at all:

  • Accessing an int at a non-4-byte-aligned address may cause a hardware exception

3. Data Corruption & Crashes

Casting misaligned memory into structs or pointers can lead to:

  • Corrupted reads/writes
  • Cache line fragmentation
  • Segmentation faults

Misaligned Scenario

Struct without padding (assuming no alignment):

struct BadLayout {
    bool b;    // offset 0
    int  i;    // offset 1 (misaligned!)
    float f;   // offset 5 (also misaligned!)
};

On a 32-bit system:

  • int and float must be aligned to 4-byte boundaries (offsets 0, 4, 8, etc.)
  • In this layout:
    • int starts at offset 1misaligned
    • float starts at offset 5misaligned
  • bool is fine at offset 0

Let’s say the CPU wants to read i at offset 1 (misaligned 4-byte access):

❌ Unaligned Read (int at offset 1):

  • CPU needs two memory fetches:
    • First: read from address 0–3
    • Second: read from address 4–7
  • Then it reconstructs the value by combining the relevant bytes

This is:

  • Slower
  • Can cause a hardware exception on strict CPUs (e.g., ARM)
  • May even result in incorrect data if you're casting

So to save the CPU cycles, and to optimize, compiler adds the padding the align the members.

What is Padding?

Padding refers to extra, unused bytes inserted by the compiler to satisfy alignment constraints.

Example:

struct Misaligned {
    bool b;     // offset 0
    int  i;     // offset 1 (!!! misaligned)
    float f;    // offset 5 (!!! also misaligned)
};

On a 32-bit system:

  • int and float must be aligned to 4-byte boundaries
  • Since i is at offset 1, it's misaligned

How Compilers Fix This: Padding

To fix the above struct, compilers insert padding bytes to restore alignment.

struct Aligned {
    bool b;     // offset 0
    char pad[3]; // offset 1–3 (compiler-added padding)
    int  i;     // offset 4
    float f;    // offset 8
};

Now:

  • i and f are 4-byte aligned
  • Struct size is 12 bytes, but only 9 bytes are used
  • The rest is padding

Example: Size of structure

#include <iostream>

struct S {
    char c;
    int i;
};

int main() {
    std::cout << "Size of S: " << sizeof(S) << "\n";
    std::cout << "Alignment of S: " << alignof(S) << "\n";
}

Output:

Size of S: 8
Alignment of S: 4

Disable Padding

1️⃣ GCC/ Clang Attribute

In GCC and clang__attribute__((packed)) is used as extension to disable the padding.

It tells the compiler to remove all padding between members of a struct/class.

That means:

  • Members are placed back-to-back
  • Alignment rules are not enforced
  • Structure becomes compact in memory

Example:

#include <iostream>

struct S {
    char c;
    int i;
}__attribute__((packed));

int main() {
    std::cout << "Size of S: " << sizeof(S) << "\n";
    std::cout << "Alignment of S: " << alignof(S) << "\n";
}

Output:

Size of S: 5
Alignment of S: 1

2️⃣ Use #pragma pack (Compiler-Specific)

#pragma pack(push, 1)
struct Packed {
    char a;
    int b;
    char c;
};
#pragma pack(pop)

Forces 1-byte alignment, output Size = 6 bytes.

But can lead to performance penalties or unsafe memory access.

3️⃣ Reorder Members

Place the larger members first.

struct PackedBetter {
    int b;
    char a;
    char c;
};

Now size becomes 8 bytes instead of 12. Always place larger members first.

⚠️ Dangers of packed

RiskDescription
Unaligned accessMay crash on hardware like ARM, MIPS, or SPARC
Slow memory fetchUnaligned reads take 2x+ more cycles on some CPUs
Undefined behaviorCasting pointers to misaligned structs is risky
Compiler-specificNot standard C++, may need porting effort

Nested Structural Padding

#include <iostream>

using namespace  std;

struct A {
    int x;
    double d;
};

struct B {
    int y;
    A a;
};

int main() {

    cout << sizeof(B) << endl;
    
}

Consider the following example:

First, Understand Alignment (64-bit system)

TypeSizeAlignment requirement
int44 bytes
double88 bytes

This means:

  • int can go at any address divisible by 4
  • double must go at an address divisible by 8

Why struct A has padding:

struct A {
    int x;		// 4 bytes
    double d;   // 8 bytes (must be aligned to 8-byte boundary)
};
  • int x starts at offset 0
  • double d must start at an address divisible by 8
  • But after x, the next byte is offset 4
  • So, the compiler inserts 4 bytes of padding between x and d.
  • Then d starts at offset 8

👉 So, sizeof(A) = 4 (x) + 4 (padding) + 8 (d) = 16 bytes

Layout of A:

OffsetMemberSizeNote
0x4int x
4–7padding4needed to align double d at 8
8–15d8double d

Why struct B has more padding:

struct B {
    int y;  // 4 bytes
    A a;    // 16 bytes (but needs to start on an 8-byte boundary)
};
  • int y starts at offset 0
  • After y, we're at offset 4
  • A contains a double, so it must be aligned on an 8-byte boundary
  • But offset 4 is not divisible by 8, so the compiler adds 4 more bytes of padding
  • Then A starts at offset 8 and takes 16 bytes

👉 So, sizeof(B) = 4 (y) + 4 (padding) + 16 (a) = 24 bytes

Layout of B:

OffsetMemberSizeNote
0y4int y
4–7padding4needed to align a (because a has a double inside)
8–23a16from 8 to 23

Why all this?

To make sure every double (and structs that contain doubles) starts on an 8-byte aligned address, which is required by the CPU for fast and correct access.

Conclusion

Alignment Rules Vary by Architecture

  • Most 32-bit and 64-bit systems align data to its size (e.g., int to 4, double to 8).
  • On some embedded systems or special compilers, the rules may differ.

Padding Affects Performance and Memory

  • More padding = more memory waste, especially in arrays of structs.
  • But removing padding (e.g., with #pragma pack) can hurt performance or crash programs on some CPUs due to misaligned memory access.

Reordering Members Reduces Padding

To minimize padding:

Place larger data types first, then smaller ones.

Example:

struct Bad {
    char c;
    int i;
}; // May use 8 bytes (1 + 3 padding + 4)

struct Better {
    int i;
    char c;
}; // Only 5 bytes needed, may use 8 still due to final padding, but no *internal* padding

Inspect Layout with offsetof

You can inspect the actual byte offsets:

#include <iostream>
#include <cstddef>

struct A {
    char c;
    int i;
};

int main() {
    std::cout << "Offset of c: " << offsetof(A, c) << "\n";
    std::cout << "Offset of i: " << offsetof(A, i) << "\n";
    std::cout << "Size of A: " << sizeof(A) << "\n";
}

Packing Structs (with Caution)

You can force smaller size with:

  • GCC/Clang:

    struct __attribute__((packed)) A { ... };
    
  • MSVC:

    #pragma pack(push, 1)
    struct A { ... };
    #pragma pack(pop)
    

⚠️ BUT: Packed structs may cause:

  • Slower memory access
  • Crashes on some CPUs
  • Issues with memory-mapped I/O or serialization

Use only when absolutely needed, like in:

  • File/network protocols
  • Embedded systems
  • Bit-level hardware control

Memory Alignment with alignas

C++11 and newer lets you control alignment:

struct alignas(16) AlignedStruct {
    int x;
};

Use this when you need specific alignment for SIMD, hardware, or performance tuning

Tools for Padding Inspection

  • sizeof() – tells total size
  • offsetof() – tells position of each member
  • Linux pahole tool – shows struct layout & padding (very useful!)
  • Static analysis tools – e.g., Clang Analyzer, GCC warnings

Leave a comment

Your email address will not be published. Required fields are marked *