Structs, Padding & Alignment
How fields sit in memory (and why gaps appear)
- › Explain how a struct lays its fields out in memory, in order
- › Define alignment and derive why the compiler inserts padding bytes
- › Predict a struct's size and each field's offset by hand
- › Reorder fields to shrink a struct, and verify with sizeof/offsetof
A struct is fields in a row
A struct groups several values into one object. In memory it's exactly what it looks like: the
fields laid out in declaration order, each at some offset from the start of the struct. Access a
field and the compiler adds that field's offset to the struct's base address — the [base + disp]
addressing mode from day 6. So far, no surprises. The one twist is padding, and it follows from a
hardware fact called alignment.
Alignment: why the CPU wants tidy addresses
Hardware reads memory most efficiently when a value of size N sits at an address that is a multiple of N (or its natural alignment): a 4-byte int prefers an address divisible by 4, an 8-byte long an address divisible by 8. A misaligned access can be slower or, on some architectures, illegal. So the compiler positions each field at a properly aligned offset, inserting unused padding bytes where needed to make the next field line up.
Watch padding appear
Consider this struct:
1struct S {2 char a; // 1 byte3 int b; // 4 bytes, wants an offset divisible by 44 char c; // 1 byte5}; offset: 0 1 2 3 4 5 6 7 8 9 10 11
┌────┬─────────┬────────────┬────┬────────┐
│ a │ PAD PAD │ b │ c │ PAD... │
└────┴─────────┴────────────┴────┴────────┘
a at 0 (1 byte). b needs offset %4==0, so 3 pad bytes -> b at 4.
c at 8. Then the STRUCT itself is padded to a multiple of its
largest alignment (4) -> total size 12.Reorder to reclaim the waste
Padding is a consequence of *order*. Put the big, strictly-aligned fields first and the small ones last, and the gaps often vanish:
1struct S2 {2 int b; // offset 03 char a; // offset 44 char c; // offset 55}; // offsets 6,7 tail-padded -> total 8 bytes, not 12Same three fields, reordered, and the struct shrank from 12 bytes to 8 — a third smaller — with no change in meaning. In tight systems code, in cache-sensitive code, and when you're reading a struct straight out of a file or a packet, this layout is not cosmetic: it decides how much memory you use and where each byte lands. The order you declare fields is a performance and correctness decision.
Ask the compiler, don't guess
sizeof(struct S) gives the total size; offsetof(struct S, b) (from <stddef.h>) gives a field's
byte offset from the start. Between them you can verify any layout you reason about — and you should,
because padding rules depend on the platform's alignment requirements.
1#include <stddef.h>2#include <stdio.h>3struct S { char a; int b; char c; };4int main(void) {5 printf("size %zu\n", sizeof(struct S)); // 126 printf("a @ %zu\n", offsetof(struct S, a)); // 07 printf("b @ %zu\n", offsetof(struct S, b)); // 48 printf("c @ %zu\n", offsetof(struct S, c)); // 89 return 0;10}size 12
a @ 0
b @ 4
c @ 8struct T { char x; double d; short s; }; (char=1, double=8 wants 8-alignment, short=2). Then the
total.#pragma pack or __attribute__((packed)) — useful
when a struct must match an exact on-disk or on-wire byte format. But packed structs can cause
misaligned accesses (slower, or faulting on some CPUs), so it's a deliberate trade, not a default.
Knowing padding lets you choose consciously instead of being surprised by sizeof.Predict a layout, then verify
In structs.c, define two structs with the SAME three or four fields in DIFFERENT orders (mix a char, an int, a double, a short). BEFORE running, hand-derive each field's offset and the total size for both, using the two padding rules. Then print sizeof and offsetof for every field and confirm your predictions, and show that reordering changed the total size. Uses only this lesson.
$ gcc -Wall structs.c -o structs && ./structs- › You derived every field offset and both totals by hand before running
- › sizeof/offsetof matched your derivations
- › Reordering the fields produced a different (smaller) total
- › You can state the two rules: align each field; pad the struct to its max alignment