Visual walkthrough — Bit fields in structs
Parent: Bit fields in structs. Here we build, picture by picture, exactly how many tiny members get squeezed into one storage box, why
sizeoflands where it does, and what happens at every awkward edge. We assume you know nothing beyond "a computer stores numbers as 0s and 1s."
Step 1 — What is a "bit" and a "storage unit"?
WHAT. A bit is a single tiny slot that holds one of two things: 0 or 1. Group a fixed number of bits together and you get a storage unit. In C the natural storage unit for bit fields is an unsigned int, which on most machines is 32 bits wide — think of a row of 32 empty slots side by side.
WHY this picture. Before we can talk about "a member that is 3 bits wide," we must agree on the shelf those bits live on. Every bit field is carved out of one of these 32-slot rows. If you have never met the word int as a box of bits, see Data types and sizeof.
PICTURE. Look at Figure s01: one long row of 32 slots, numbered from bit 0 on the right to bit 31 on the left. That whole row is one unsigned int.

Step 2 — Reserving slots: member : width
WHAT. The syntax unsigned int a : 5; means "give member a exactly 5 slots." We are not storing a full 32-bit integer — we are renting 5 of the 32 slots and leaving the other 27 free for later members.
WHY this tool and not a normal int? A normal int member always grabs a whole row (32 slots) even to store a yes/no flag. The : width notation is the only way in C to say "I need fewer slots than a whole type." That is the entire point of Bit fields in structs.
PICTURE. Figure s02 shades the first 5 slots (bits 0–4) as member a. Notice the arrow: the compiler keeps a marker called the offset pointing at the first still-free slot — right now it points at bit 5.

Step 3 — Placing a second field: advancing the offset
WHAT. Add unsigned int b : 5;. The compiler reads its offset marker (pointing at bit 5), drops b into the next 5 free slots (bits 5–9), then pushes the offset to bit 10.
WHY. This is packing: consecutive small fields sit shoulder-to-shoulder in the same row, no gaps. That is where the memory saving comes from — three separate ints would cost 3 rows; here a and b share one.
PICTURE. In Figure s03 you see two shaded blocks in one row: a (bits 0–4) and b (bits 5–9). The offset arrow has slid right to bit 10. Slots 10–31 are still empty.

Step 4 — The overflow of the row: when a field won't fit
WHAT. Now add unsigned int c : 25;. The offset sits at bit 10, so only slots remain. But c demands 25. It cannot fit in the leftover 22.
WHY. A single bit field is not allowed to straddle the boundary of a storage unit here (this "no-split" behaviour is what forces a new row). So the compiler abandons the leftover slots and starts c at the beginning of a fresh 32-slot row.
PICTURE. Figure s04 shows row 1 with a, b, and 22 wasted slots, and a brand-new row 2 holding c (bits 0–24). Two rows = 8 bytes total.

Step 5 — Filling the slots: reading the value in a field
WHAT. Store a number into a field, e.g. b = 13. The compiler writes 13 in binary into b's 5 slots: , which fits (5 slots hold ).
WHY. The bits inside a field are read as an unsigned number in place — the rightmost rented slot is the ones place, next is the twos place, and so on, exactly like Unsigned vs signed integers with fewer digits.
PICTURE. Figure s05 zooms into b's 5 slots and labels each with its place value , showing the pattern summing to .

Each digit is multiplied by the place value written beneath it, and the sum is the stored value.
Step 6 — Overflow of a field: the digits that fall off
WHAT. Assign s.v = 13 to a 3-slot field unsigned v : 3. But needs 4 slots — one too many. C does not error. It keeps only the low 3 slots and discards the rest.
WHY. Keeping "the low bits" is arithmetically the same as taking the remainder after dividing by — because every bit above position carries a value that is a multiple of , and dividing by throws exactly those away.
PICTURE. Figure s06 shows the 4-bit pattern arriving, the top 1 sliding off the left edge (red), leaving in the field.

Step 7 — The sign trap: signed fields lose the top slot
WHAT. Declare signed int x : 3;. It feels like it stores (3 slots = 8 values). It does not. The top (leftmost) rented slot becomes a sign flag: 1 there means "this is negative."
WHY. Signed integers use two's complement: the highest slot carries a negative weight. With 3 slots the weights are , so the reachable numbers run — still 8 values, but shifted below zero. See Unsigned vs signed integers.
PICTURE. Figure s07 compares two 3-slot fields side by side: unsigned (weights , range ) versus signed (weights , range ).

Step 8 — The forced boundary: unsigned : 0;
WHAT. An unnamed, zero-width field unsigned : 0; rents nothing but does one job: it jumps the offset straight to the start of the next storage unit.
WHY. Sometimes hardware demands a field begin exactly at a fresh 32-bit boundary (a register split across two words, say — the world of Embedded systems / hardware registers). The zero-width field is the deliberate "skip to the next row" instruction; it is the Memory alignment and padding control for bit fields.
PICTURE. Figure s08 shows low : 4 in row 1, the zero-width marker firing, and high : 4 beginning fresh at bit 0 of row 2.

struct Reg {
unsigned low : 4; // row 1, bits 0..3
unsigned : 0; // JUMP to next unit
unsigned high : 4; // row 2, bits 0..3
};The one-picture summary
Figure s09 threads the whole story into one diagram: a field is rented slots → the offset bookmark advances → a field that overruns a row starts a new row → a value too big for a field has its top bits fall off (mod ) → a signed field sacrifices its top slot → and : 0 forces a fresh row.

Recall Feynman: retell the whole walkthrough in plain words
Picture a shelf with 32 tiny slots. When you write a : 5, you rent the first 5 slots and put a bookmark right after them. Ask for b : 5 and it sits in the next 5 slots, bookmark shuffles along. Ask for a 25-slot field when only 22 slots are left, and it refuses to split across the edge — so it grabs a whole new shelf, wasting the leftover slots (that is why the struct grows to 8 bytes). Inside a field, the slots are just place values ; store 13 in a 3-slot field and the "8" part has nowhere to sit, so it drops off and you keep — the same as . If the field is signed, its top slot flips to mean "minus," so a 3-slot signed field runs to instead of to . And if you ever want a field to start at the very front of a fresh shelf, drop in unsigned : 0; — it rents nothing but shoves the bookmark to the next shelf's slot 0.
Connections
- Bit fields in structs — the parent this walkthrough visualises.
- Structs in C — the container these members live in.
- Bitwise operators — the manual
& | << >>version of the same slot-shuffling. - Data types and sizeof — why the storage unit is an
int. - Memory alignment and padding — what
unsigned : 0;controls. - Unsigned vs signed integers — the two's-complement top-slot sign.
- Embedded systems / hardware registers — why forced boundaries matter.