Layoutalignmentnatural alignmentunaligned accessISAmemory

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.

▶ Run the labFollow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
Why does the address a value sits at matter, when memory is supposedly just bytes?
What you wrote
A 4-byte integer is 4 bytes. It can live at any address; the compiler will figure it out.
What the hardware does
The memory system moves fixed-width blocks at aligned boundaries. A value that straddles a boundary requires two accesses that must be combined, and on some architectures the instruction simply faults instead.
Alignment is the reason 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.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

Natural alignment, and what straddling costs

SIMPLIFIED64-byte lines are common on current x86-64 and many AArch64 parts, but line size is a microarchitectural parameter and must be queried, not assumed.

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.

A 4-byte value straddling a cache-line boundary. One logical load, two lines touched.
another thread's datausedSIMPLIFIED
earlier datarest of next line
line 0line 1
128 bytes total2 cache lines touched

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

ISA-SPECIFICx86-64 tolerates unaligned scalar access in hardware; AArch64 tolerates it for most ordinary loads but faults for exclusive and some atomic forms; several embedded and older RISC targets fault on any misaligned access.

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.

Unaligned access behaviour is architecture-dependent — this is why the language forbids it rather than defining a cost
SituationTypical behaviourCost
Aligned accessSingle access to one aligned blockBaseline
Unaligned, within one cache lineHardware splits and recombines, where supportedSmall, often unmeasurable
Unaligned, crossing a cache lineTwo line accesses combinedNoticeable; doubles miss exposure
Unaligned, crossing a pageTwo translations, possibly two faultsPotentially severe
Unaligned where the ISA forbids itAlignment fault; OS traps and emulates, or the process diesOrder of magnitude, or fatal
Unaligned atomic or vector opFrequently faults even where scalar access would notFatal 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.

The portable way to read a wider value out of a byte buffer
1// UNSAFE: the cast may be unaligned, and it is undefined
2// behaviour in C/C++ regardless of whether it happens to work
3value = *(uint32_t*)(buffer + offset)
4
5// SAFE: copy the bytes into an aligned variable.
6// Compilers routinely turn this back into a single load
7// when the target supports unaligned access anyway.
8uint32_t value
9memcpy(&value, buffer + offset, sizeof(value))
10
11// And if the value came off a network or a file, the bytes
12// 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.

Field order decides the size
Declared in a natural reading order
usedpaddingABI-SPECIFIC
padint bpaddouble d
line 0
24 bytes total1 cache line touched10 bytes of padding

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.

  1. 1
    Compiler → layout: each field is placed at an offset satisfying its natural alignment, inserting padding where needed.
  2. 2
    Load → cache: an aligned access maps to exactly one aligned block within one cache line.
  3. 3
    Unaligned load → cache: the access spans two blocks, so the hardware must fetch both and merge the result, or fault if the ISA forbids it.
  4. 4
    Cache line boundary → MMU: if the two halves fall in different pages, two address translations are required and both must be resident.
  5. 5
    OS trap → emulation: on architectures that fault, the kernel may emulate the access in software at roughly an order of magnitude more cost.
What people conclude from this — wrongly
  • "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

What it causes
  • • 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.
What you can do
  • • 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.
How to see it
  • • 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.
What it costs
  • • 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.

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

Claim
“Alignment only matters on old or embedded hardware.”
Reality
It still determines struct padding everywhere, still governs cache-line and page straddling, and still faults for certain atomic and vector instructions on current architectures.
Claim
“Unaligned access costs a fixed small penalty.”
Reality
It costs nothing within a line on tolerant architectures, materially more across a line, potentially a page fault across a page, and is fatal where the ISA forbids it.
Claim
“If the cast compiles, it is legal.”
Reality
Casting a misaligned pointer to a wider type is undefined behaviour in C and C++ even on hardware that would execute it correctly.

Apply it

Where the rest of this lives

Programming Languages & Runtime Internals
Object layout and undefined behaviour

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.