2026-09-04
Every load and store your CPU issues has an address, and every access has a natural alignment: a 4-byte load "wants" to sit on a 4-byte boundary, an 8-byte store on an 8-byte boundary. What happens when it doesn't? That depends on the ISA, the memory type, and a bit in a control register you probably didn't know existed.
Three ISA philosophies:
The AC flag nobody uses: x86 has an Alignment Check bit (EFLAGS.AC) plus CR0.AM. Set both and every unaligned user-mode access raises #AC. Nobody enables it because glibc's memcpy is full of intentional misalignment.
The SIMD wrinkle: Old SSE required 16-byte alignment for MOVAPS — misaligned access faulted. MOVUPS tolerated it but was slower. Since Nehalem (2008), MOVUPS on aligned data has the same throughput as MOVAPS, so the distinction is mostly historical. AVX-512 loads/stores tolerate misalignment but a cache-line split still costs a port cycle and doubles the load queue entry.
Concrete example — the 4KB page split penalty: An unaligned 8-byte load crossing a 4KB page boundary requires two TLB lookups plus two cache lookups. On Skylake this costs ~100 cycles versus ~4 for an aligned load. A struct like {char pad[4093]; uint64_t counter;} straddles the boundary; increment it in a hot loop and you'll see it in perf stat -e ld_blocks_partial.address_alias.
Rule of thumb:
Align hot atomics and lock words to their natural size — the compiler does this for _Atomic and std::atomic, but packed structs and network protocol parsers routinely defeat it. When __attribute__((packed)) shows up in a hot path, count the alignment cost before shipping.
