Reading Assembly

Part 3 · Reading compiler output

Structs, padding and layout

Field order changes how big your struct is. Not by a little, and not in a way you can guess without knowing the rule.

30 mincontrasting casesPOE
What this lesson is trying to break (3)
  • A struct is the sum of its fields.
  • Field order is a style choice.
  • Rust structs lay out in declaration order.

Alignment is a machine constraint, not a language one, and it produces bytes you did not ask for.

The rule

Two clauses:

  1. Each field sits at an offset that is a multiple of its own alignment. For primitives, alignment equals size: a 4-byte int must be at an offset divisible by 4.
  2. The struct’s total size is a multiple of its largest alignment.

Clause 2 is the one people forget, and it exists for arrays: if sizeof(S) were not a multiple of S’s alignment, then arr[1] would be misaligned even though arr[0] was fine.

Predict firstcommit in writing, then look
struct A { char a; int b; char c; };
struct B { int b; char a; char c; };

Same three fields, different order. What is sizeof each?

Seeing it without a debugger

You can read layout straight out of the assembly. Compile this:

struct S { char a; int b; char c; };

int get_b(struct S *s) { return s->b; }
int size(void)         { return sizeof(struct S); }

size returns a constant — the compiler folded sizeof — so it tells you the total directly. And get_b shows you the offset: you will see a load from [rdi+4], which says b lives at offset 4, which tells you three bytes of padding preceded it.

That is a genuinely useful technique. Given a binary and no headers, field offsets are readable from the loads that access them.

Rust does something different

Rust makes no layout guarantee for ordinary structs. It is free to reorder fields, and it does, precisely to minimise padding. So:

struct A { a: u8, b: u32, c: u8 }   // Rust: 8 bytes, reordered internally

Rust gives you the packed layout automatically, where C makes you earn it by declaring in the right order.

The cost of that freedom is that you cannot depend on the layout — which is what #[repr(C)] is for:

#[repr(C)]
struct A { a: u8, b: u32, c: u8 }   // 12 bytes, C rules, declaration order

You need #[repr(C)] whenever layout is observable from outside: FFI, memory-mapped hardware, wire formats, anything you transmute. Using it unnecessarily means opting back into padding you did not need.

Beaconsee this → think this, without decoding it

#[repr(C)] · #[repr(packed)] · #[repr(transparent)]

  • repr(C) — C layout and declaration order. Something outside Rust sees this type.
  • repr(packed) — no padding at all. Fields may be misaligned, so reads may be slower or, on some targets, trap. References to misaligned fields are undefined behaviour, which is why packed is genuinely dangerous rather than merely unusual.
  • repr(transparent) — a single-field wrapper with identical layout to its field. The newtype pattern’s guarantee that it costs nothing.
Explain it to yourselfwrite it — thinking it does not count

You have a struct used in an array of ten million elements, currently 24 bytes, of which 7 are padding.

Give two independent reasons shrinking it to 17 (or 20 after tail padding) would speed up a loop that scans the array — one about memory volume and one about cache lines. Then say why the second reason usually matters more.

What you should now be able to say

  • Fields must be at offsets that are multiples of their alignment, and struct size must be a multiple of its largest alignment.
  • Field order changes sizeof — a third of a badly-ordered struct can be padding. Declare largest-first.
  • Field offsets are readable from load instructions; sizeof folds to a constant.
  • Rust reorders to minimise padding; #[repr(C)] opts out for FFI and observable layout; repr(packed) removes padding and can cause UB.

Next: 3.6 — Verifying “zero-cost”.

In the wildgo read the real thing now, while it is fresh
  • The Lost Art of Structure PackingThe canonical treatment. Read sections 1 to 4. It is C-focused but the alignment rules are the machine's, not C's, so it applies everywhere.
Scheduled for recall3 items added to your review queue

These are not shown to you now on purpose. They will surface in Review tomorrow, mixed in among items from other parts of the course — not grouped by topic. Sorting out which idea applies is the part that transfers.