roadmap
week 3 · day 18

Structs, Padding & Alignment

How fields sit in memory (and why gaps appear)

Medium 32 min 180 xp
After this you can
  • 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:

s.cc
1struct S {
2 char a; // 1 byte
3 int b; // 4 bytes, wants an offset divisible by 4
4 char c; // 1 byte
5};
layout of struct S (12 bytes, not 6)
  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.
Key idea
Two rules generate the whole layout: (1) each field starts at the next offset that satisfies its alignment (insert padding to get there); (2) the struct's total size is rounded up to a multiple of its largest field's alignment (so arrays of the struct stay aligned). Here that's 1 (a) + 3 pad + 4 (b) + 1 (c) + 3 tail-pad = 12 bytes, even though the data is only 6. You *derive* the size; you never memorize it.

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:

s2.cc
1struct S2 {
2 int b; // offset 0
3 char a; // offset 4
4 char c; // offset 5
5}; // offsets 6,7 tail-padded -> total 8 bytes, not 12

Same 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.

layout.cc
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)); // 12
6 printf("a @ %zu\n", offsetof(struct S, a)); // 0
7 printf("b @ %zu\n", offsetof(struct S, b)); // 4
8 printf("c @ %zu\n", offsetof(struct S, c)); // 8
9 return 0;
10}
Expected output
size   12
a @ 0
b @ 4
c @ 8
Predict first
Predict the size and each field's offset for: struct T { char x; double d; short s; }; (char=1, double=8 wants 8-alignment, short=2). Then the total.
Note
You can force tight, no-padding layout with #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.
finished reading?
Your task, you write the code

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.

deliverable: structs.c (+ your by-hand layout derivations)
build & run
$ gcc -Wall structs.c -o structs && ./structs
self-review before running
  • 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
stretchAdd __attribute__((packed)) to one struct and see the size drop to the raw sum with no padding. Then read one field from the packed struct in a loop and, if you can, compare timing — the cost of misalignment made real.

Self-check

01Why can sizeof(struct) be larger than the sum of its fields?
02A 4-byte int field prefers to start at an offset that is:
03Reordering a struct's fields can:
04How do you verify a struct's layout instead of guessing?
0/4 correct · 0/4 checked