Memory alignment in C structs governs how data members are positioned in memory relative to address boundaries. This behavior directly impacts memory footprint, cache efficiency, and hardware-level access correctness—especially on architectures that enforce strict alignment (e.g., ARM64 or older SPARC). Unlike high-level languages, C exposes this low-level detail explicitly, making alignment awareness essential for systems programming, embedded development, and interoperability with binary protocols.
Core Alignment Rules
In standard C, the alignment requirement of a struct is determined by the strictest alignment among its members. Each member must begin at an offset divisible by its natural alignment (typically equal to its size for fundamental types like int, double, or pointers). The compiler inserts padding bytes between members—and potentially after the last member—to satisfy these constraints and ensure proper alignment when the struct is arrayed.
For example:
#include <stdio.h>
#include <stdalign.h>
struct ExampleA {
char x; // offset 0
int y; // offset 4 (padded 3 bytes after x)
short z; // offset 8 (no padding needed; int aligns to 4, short to 2)
}; // total size: 12 bytes (with 2 bytes of trailing padding to make sizeof() a multiple of max alignment = 4)
struct ExampleB {
int y;
char x;
short z;
}; // offset 0→4 (y), 4→5 (x), 6→8 (z, padded 1 byte); size = 8 (no trailing pad needed since 8 % 4 == 0)
This illustrates how reordering fields minimizes padding: ExampleB uses only 8 bytes versus 12 in ExampleA.
Controlling Alignment Explicitly
C11 introduces _Alignas for portable alignment specification, while GCC/Clang support __attribute__((aligned(N))). These directives override default alignment but never relax it—only increase minimum alignment guarantees.
Consider a cache-line–optimized structure:
struct CacheAlignedHeader {
uint32_t flags;
uint64_t timestamp;
} __attribute__((aligned(64))); // forces 64-byte alignment
// Even if sizeof(struct CacheAlignedHeader) is 16,
// each instance starts at a 64-byte boundary.
Such control is vital for lock-free data structures, DMA buffers, or SIMD-processed arrays where misalignment causes performanec penalties or hardware faults.
Practical Implications
- Serialization: Padding bytes contain indeterminate values. Direct
memcpyor file writes of structs may transmit garbage. Always serialize/deserialize field-by-field or use packed attributes cautiously. - Interfacing with Hardware/APIs: Device registers or network packet layouts often require exact byte offsets. Use
__attribute__((packed))sparingly—and only when combined withvolatileor explicit memory barriers—since unaligned access can crash or silent truncate on some platforms. - Dynamic Allocation: When allocating arrays of aligned structs (e.g., via
aligned_alloc), insure both buffer alignment and element spacing respect the struct’s alignment constraint.
Verifying Layout
Use offsetof (from <stddef.h>) and _Alignof to introspect layout at compile time:
#include <stddef.h>
#include <stdalign.h>
static_assert(offsetof(struct ExampleB, y) == 0, "y must start at offset 0");
static_assert(_Alignof(struct ExampleB) == 4, "struct alignment must be 4");
These assertions prevent silent breakage during refactoring or porting.