Alignment: Why Addresses Are Not Arbitrary
Hardware prefers a four-byte value at an address divisible by four. Break that and the penalty ranges from literally nothing, through a silent extra memory access, to a fault that kills the process — and which one you get depends entirely on the architecture.
Software view, hardware view
The gap between what you wrote and what the machine does is where this whole domain lives.
sizeof a struct is larger than the sum of its fields, the reason casting a byte pointer to an integer pointer is undefined behaviour in C and C++, and the reason a binary format that ignores alignment can be both slower and non-portable.Natural alignment, and what straddling costs
A value is naturally aligned when its address is a multiple of its size: a 4-byte integer at an address divisible by 4, an 8-byte double at an address divisible by 8. Hardware is built around this assumption because the paths between cache and register move fixed-width aligned chunks. An aligned 4-byte load is one operation on one chunk.
An unaligned value can straddle two of those chunks — and in the worst case two different cache lines, which means it can straddle two different pages and therefore require two address translations, potentially two TLB entries and, in a pathological case, two page faults. What looked like one load becomes a small cascade.
The layout below shows a 4-byte integer placed at offset 62 of a 64-byte line. Three of its bytes are in one line and one is in the next. Everything the memory system does for this load, it now does twice.
The two int cells are one value split across the line boundary at byte 64. Reading it requires both lines to be present, doubling the miss exposure of a single access.
What actually happens differs wildly by architecture
This is a §224 case where the behaviour genuinely does not generalise, and stating a single answer would be wrong. On x86-64, ordinary integer loads and stores handle unaligned addresses in hardware; the cost is usually small and often unmeasurable unless the access crosses a cache line. On many AArch64 configurations ordinary loads also tolerate misalignment, but some instruction classes — notably certain atomic and exclusive-access forms — require alignment and fault without it.
On stricter architectures, and on some embedded targets, an unaligned access raises an alignment fault outright. The operating system may trap and emulate it in software, which is correct but roughly an order of magnitude slower than the aligned access, or it may simply deliver a fatal signal.
So the honest programmer-level rule is not "unaligned is slow". It is: unaligned is undefined at the language level and unpredictable at the hardware level, and the cost ranges from zero to fatal depending on where the code runs.
| Situation | Typical behaviour | Cost |
|---|---|---|
| Aligned access | Single access to one aligned block | Baseline |
| Unaligned, within one cache line | Hardware splits and recombines, where supported | Small, often unmeasurable |
| Unaligned, crossing a cache line | Two line accesses combined | Noticeable; doubles miss exposure |
| Unaligned, crossing a page | Two translations, possibly two faults | Potentially severe |
| Unaligned where the ISA forbids it | Alignment fault; OS traps and emulates, or the process dies | Order of magnitude, or fatal |
| Unaligned atomic or vector op | Frequently faults even where scalar access would not | Fatal or specially handled |
Where alignment shows up in code you actually write
The most common encounter is struct layout, where the compiler inserts padding specifically to keep each field naturally aligned — the subject of Padding: Why Your Struct Is Bigger Than Its Fields. The second most common is parsing binary data: casting a pointer into a received byte buffer to a wider type is both undefined behaviour and potentially unaligned, and the portable fix is to copy the bytes into a properly aligned variable.
The third is deliberate over-alignment. Vector instructions often perform better with operands aligned to the vector width, and cache-line alignment is the standard remedy for False Sharing: Independent Data, Shared Line. Languages expose this: C11 and C++11 have alignas, Rust has #[repr(align(N))], and allocators have aligned-allocation entry points.
The general principle is that alignment is almost always handled correctly for you by the compiler, and the times you must think about it are exactly the times you are stepping outside the type system: raw buffers, memory-mapped hardware, custom allocators and manual serialisation.
1// UNSAFE: the cast may be unaligned, and it is undefined2// behaviour in C/C++ regardless of whether it happens to work3value = *(uint32_t*)(buffer + offset)4 5// SAFE: copy the bytes into an aligned variable.6// Compilers routinely turn this back into a single load7// when the target supports unaligned access anyway.8uint32_t value9memcpy(&value, buffer + offset, sizeof(value))10 11// And if the value came off a network or a file, the bytes12// still need interpreting in a defined order -- see [[endianness]].Key points
- A value is naturally aligned when its address is a multiple of its size; hardware paths are built around that assumption.
- Unaligned access can straddle cache lines and pages, turning one logical access into two with double the miss exposure.
- Behaviour is genuinely ISA-specific: tolerated in hardware, trapped and emulated by the OS, or fatal, depending on the target.
- Compilers handle alignment automatically inside the type system; problems arise when you leave it, with raw buffers and casts.
- Deliberate over-alignment is a real tool for vector operands and for avoiding false sharing.
Struct Layout & Padding
Change an input and watch which number moves — and which one refuses to.
Each field must sit at an address that is a multiple of its size, so the compiler inserts padding to get there. Twenty-four bytes to hold fourteen bytes of data, and in an array of a million records that is ten megabytes of nothing.
Follow the mechanism
The path through the machine, hop by hop — and the conclusions it invites that are wrong.
- 1Compiler → layout: each field is placed at an offset satisfying its natural alignment, inserting padding where needed.
- 2Load → cache: an aligned access maps to exactly one aligned block within one cache line.
- 3Unaligned load → cache: the access spans two blocks, so the hardware must fetch both and merge the result, or fault if the ISA forbids it.
- 4Cache line boundary → MMU: if the two halves fall in different pages, two address translations are required and both must be resident.
- 5OS trap → emulation: on architectures that fault, the kernel may emulate the access in software at roughly an order of magnitude more cost.
- • "Unaligned access works on my machine, so it is fine." It is undefined behaviour in C and C++, and the machine it fails on is the one you have not tested.
- • "The penalty is always small." Within a cache line it usually is; across a page boundary it can be severe, and on strict architectures it is fatal.
- • "Alignment is a compiler concern, not mine." True until you touch raw buffers, memory-mapped I/O, custom allocators or serialisation.
Consequences, controls and cost
- • A struct occupies more space than the sum of its fields, which changes how many of them fit in a cache line.
- • Binary parsing code that casts into a raw buffer is non-portable and can crash on architectures that fault.
- • Vector code can lose measurable performance, or fail outright, when operands are not aligned to the vector width.
- • Stay inside the type system: let the compiler lay out structs, and use `memcpy` rather than pointer casts to read from raw buffers.
- • Use `alignas` or the equivalent when you deliberately need stronger alignment for vectors or cache lines.
- • Order struct fields to minimise padding when the type is used in large arrays — see [[padding-and-struct-layout]].
- • On targets that fault, enable the compiler and sanitiser checks that catch misaligned access before production does.
- • Compare throughput reading a value at every offset within a line — a spike at the offset that crosses the boundary isolates the penalty.
- • Enable alignment sanitisers or the architecture's alignment-check facility to catch misaligned accesses during testing.
- • Inspect `sizeof` and field offsets directly to confirm what the compiler actually laid out rather than what you assumed.
- • Over-alignment wastes memory, and cache-line-aligning many small objects can inflate a working set enough to cause misses elsewhere.
- • Reordering struct fields for packing can hurt readability and disrupt an order chosen to match a wire format or a domain concept.
Scope
§224 — what these claims are specific to.
- ISA-SPECIFICUnaligned tolerance differs fundamentally: x86-64 handles it in hardware for scalar access, AArch64 mostly does but faults for exclusive forms, and several embedded targets fault on any misalignment.
- ABI-SPECIFICNatural alignment requirements for each type, and therefore struct padding, are defined by the platform ABI rather than by the language.
Misconceptions
Apply it
Where the rest of this lives
Whether a misaligned access is undefined, checked or simply slow is decided by the language and its runtime as much as by the hardware; C and C++ forbid it, managed runtimes prevent it structurally.