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
andfloat
(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
- Many types (like
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
andfloat
must be aligned to 4-byte boundaries (offsets 0, 4, 8, etc.)- In this layout:
int
starts at offset 1 → misalignedfloat
starts at offset 5 → misaligned
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
andfloat
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
andf
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
Risk | Description |
---|---|
Unaligned access | May crash on hardware like ARM, MIPS, or SPARC |
Slow memory fetch | Unaligned reads take 2x+ more cycles on some CPUs |
Undefined behavior | Casting pointers to misaligned structs is risky |
Compiler-specific | Not 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)
Type | Size | Alignment requirement |
---|---|---|
int | 4 | 4 bytes |
double | 8 | 8 bytes |
This means:
int
can go at any address divisible by 4double
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 0double 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
andd
. - Then
d
starts at offset 8
👉 So, sizeof(A)
= 4 (x) + 4 (padding) + 8 (d)
= 16 bytes
Layout of A:
Offset | Member | Size | Note |
---|---|---|---|
0 | x | 4 | int x |
4–7 | padding | 4 | needed to align double d at 8 |
8–15 | d | 8 | double 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 adouble
, 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:
Offset | Member | Size | Note |
---|---|---|---|
0 | y | 4 | int y |
4–7 | padding | 4 | needed to align a (because a has a double inside) |
8–23 | a | 16 | from 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 sizeoffsetof()
– 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 *