Embedded C
Alignment & Padding
C's Structs & Unions mentioned, as a gotcha, that a struct's size is often larger than the sum of its fields. This chapter is why: CPUs read memory fastest — sometimes exclusively — at specific address boundaries, and the compiler inserts invisible filler bytes to guarantee every field lands on one, whether you asked for it or not.
- What it is
- The rule that a type's address must be a multiple of its own alignment requirement, and the compiler-inserted padding bytes that make that true inside a struct.
- Mental model
- Parking spaces painted only every 4 meters — a car (a 4-byte
int) can only park on a painted line, even if that leaves an empty gap behind the car in front of it. - Key numbers
- A type's alignment requirement commonly equals its own size (a 4-byte
intwants a 4-byte-aligned address); a struct's overall alignment is the strictest of any of its members. - Reach for it when
- Minimizing a struct's memory footprint on a RAM-constrained target, or explaining an unexpectedly large
sizeof(struct ...).
Why alignment exists
Many CPUs read memory in fixed-size chunks (words) starting only at addresses that are multiples of that
chunk size. Reading a 4-byte int that happens to start at an address that isn't a
multiple of 4 either costs extra CPU cycles (the processor fetches two words and stitches the value
together) or, on some architectures, isn't allowed at all and faults immediately. The compiler avoids
both outcomes by never placing a field at a misaligned address in the first place.
Padding, in a worked example
struct Example {
char a; // 1 byte
int b; // 4 bytes — needs a 4-byte-aligned address
char c; // 1 byte
};
printf("%zu\n", sizeof(struct Example)); // 12, not 1 + 4 + 1 = 6
b, one so a second element in an array would also start aligned.Reordering fields to save space
The compiler is not allowed to reorder your struct's fields — but you can, and doing so can meaningfully shrink a struct by grouping same-size (or naturally-aligned) fields together:
struct Wasteful {
char a; // 1 byte + 3 padding
int b; // 4 bytes
char c; // 1 byte + 3 padding
}; // sizeof = 12
struct Tight {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte + 2 padding (to keep the struct itself 4-byte aligned)
}; // sizeof = 8 — same fields, smaller struct
A simple, reliable rule of thumb: declare a struct's fields from largest to smallest.
Checking a struct's real size
Never guess a struct's size — measure it, and on a target where every byte of RAM matters, treat an
unexpectedly large sizeof result as worth investigating:
printf("Wasteful: %zu bytes\n", sizeof(struct Wasteful)); // 12
printf("Tight: %zu bytes\n", sizeof(struct Tight)); // 8
Real-world applications
- RAM-constrained firmware — reordering a handful of frequently-instantiated structs (a sensor reading, a queue entry) for tighter packing is a real, measurable win on a device with only a few KB of RAM total.
- Arrays of structs — padding waste multiplies by the array length — a struct with 4 wasted bytes costs 4KB across a 1000-element array, not just 4 bytes.
- Matching an external layout — when a struct must match a hardware register map or wire format exactly, padding is the enemy, not a neutral side effect — see Structs, Unions & Bit-Fields' packed structs section.
Gotchas
- Padding bytes contain unspecified, uninitialized garbage — comparing two otherwise-identical structs byte-by-byte with
memcmpcan report them as different purely because of differing padding content, even when every actual field matches. - Field reordering for size is a real, measurable optimization, but it's also a genuine readability/grouping tradeoff — reordering fields purely by size can scramble a struct's logical grouping; weigh both before reordering a struct that's read by other engineers.
- Alignment requirements and default padding rules are not identical across every compiler and architecture — a struct's exact size on one target is not guaranteed on another, which matters whenever binary data crosses that boundary (see Serialization for the standard fix: define an explicit wire format instead of relying on a struct's native in-memory layout).
- Forcing a packed layout with
#pragma packto eliminate padding can reintroduce the exact misaligned-access cost (or fault) alignment exists to prevent — only pack a struct when matching an external layout genuinely requires it, not as a default habit.