← Binary Course — Part B, lesson 4 of 4 (Bitwise operations)
8. Packing Multiple Fields Into One Word
The concept
Lesson 7 read and wrote individual bits inside a word. This lesson does the same thing at the scale a real program actually uses it: dividing one word into several named fields, each holding its own small value, packed side by side instead of each getting a whole word to itself. A 4096-word memory where every word is sliced into, say, three fields behaves — for the purpose of how much distinct information it can hold — like a much larger number of small storage slots.
Take an 8-bit word divided into three fields, agreed on in advance:
bit index: 7 6 5 4 3 2 1 0
[type ][ value ][flags]
type lives at bits 7-6 (2 bits), value at bits 5-2 (4 bits), flags
at bits 1-0 (2 bits). Reading one of these back out is exactly Lesson 7
Example 2: mask, then shift right by the field's starting bit. Writing one
in is the new piece, and it splits into two cases:
- Writing into a field that's currently all
0s (a fresh word, or a field never touched yet): shift the new value left by the field's starting bit to put it in position, then OR it into the word — Lesson 7 Example 4's single-bit OR, generalized to a multi-bit field. - Writing into a field that already holds something: shifting the new
value into position and OR-ing it in is not enough — OR only ever
turns bits on, so any
1left over from the field's old value stays stuck on, merging with the new value instead of being replaced by it. The field has to be cleared to0first (Lesson 7 Example 5's AND-with- a-NOT'd-mask), then the new value is shifted into position and OR'd in. Clear, then set — never just set.
Two things worth being careful about with the "shift, then OR" recipe.
First: what gets OR'd in always has to be the value's own bits, shifted
into position — never a mask (all 1s over the field). The two can look
deceptively similar. Writing type = 2 (0b10) into bits 7-6 means
OR-ing in 1000 0000, the shifted value. OR-ing in 1100 0000 instead
— the field's mask — sets both field bits to 1 regardless of what
value was wanted, storing 3 instead of 2. This doesn't go away even
when the value being written is a fixed constant known in advance: a
compiler (or a programmer working by hand) can skip running a shift
instruction and just write the already-shifted answer as a literal, but
that literal still has to be the shifted value, not the mask. A mask is a
different tool for a different job (Lesson 7's AND-based read and clear
recipes); it's never the right thing to OR into a word.
Second: shifting only repositions a value, it doesn't check that the
value actually fits the field. A 4-bit field can only hold 0-15; if
the number being packed into it is 17 (10001, five bits), the shift
carries that fifth bit right along and it lands one position into the
next field over, corrupting it silently — the same overflow idea from
Lesson 6, just showing up at a field boundary instead of the top of the
whole word. Packing code has to know each field's width and either trust
the value fits or check it first; the shift-and-OR recipe itself has no
way to catch an oversized value on its own.
Example 1 — Writing a field into a virgin word
Start from an all-zero word and write type = 2 (0b10) into bits 7-6:
type value: 10
shifted (<<6): 1000 0000
word (was all 0): 0000 0000
word OR shifted: 1000 0000
word = 1000 0000 — only bits 7-6 changed, because a word of all 0s
ORs against anything without altering it.
Example 2 — Packing a second field alongside the first
Continue from Example 1's word and write value = 9 (0b1001) into bits
5-2:
value: 1001
shifted (<<2): 0010 0100
word (from Ex. 1): 1000 0000
word OR shifted: 1010 0100
word = 1010 0100. Bits 5-2 now hold 1001; bits 7-6 are untouched,
because they were 0 in the shifted value being OR'd in. This is the same
"OR with 0 leaves it alone" guarantee from Lesson 5, just relied on
across a whole field's width instead of one bit.
A flags = 01 written the same way, into the still-untouched bits 1-0,
brings the word to 1010 0101 — every field now holds something.
Example 3 — Updating a field that's already occupied
Now change flags from 01 to 10 in that same word,
1010 0101. First, the wrong way — shift the new value into position and
OR it straight in, the way Examples 1 and 2 did:
new flags: 10
shifted (<<0): 0000 0010
word: 1010 0101
word OR shifted: 1010 0111 ← wrong
Reading bits 1-0 back out of 1010 0111 gives 11, not 10. The old
flags bit at bit 0 (1, left over from 01) was still sitting there, and
OR only ever turns bits on — it merged with the new value instead of
replacing it (01 OR 10 = 11).
The right way clears the field first. Build a mask with 0s over bits
1-0 and 1s everywhere else by NOT-ing 0000 0011:
field mask: 0000 0011
NOT (clear mask): 1111 1100
word: 1010 0101
word AND clear mask: 1010 0100 ← flags cleared to 00
OR shifted new value: 1010 0110 ← correct
word = 1010 0110. Bits 1-0 now correctly read 10, and nothing else in
the word moved.
Example 4 — Unpacking every field back out
Take Example 3's final word, 1010 0110, and read all three fields back
out using Lesson 7 Example 2's mask-then-shift method:
field mask word AND mask >> (start bit) value
type 1100 0000 1000 0000 >> 6 2
value 0011 1100 0010 0100 >> 2 9
flags 0000 0011 0000 0010 >> 0 2
type = 2, value = 9, flags = 2 (0b10) — exactly what was written
across Examples 1 through 3. Three independent values, recovered whole
from one 8-bit word.
Exercises
Exercise 1. Starting from an all-zero word using this lesson's layout,
write value = 6 (0b0110) into bits 5-2. Show the shifted value and the
resulting word.
Exercise 2. Take Example 3's final word, 1010 0110. Extract the
value field (bits 5-2) and give the result in hex, using Lesson 3's
nibble table. (The field's value fits in one hex digit.)
Exercise 3. A classmate has the word from Example 2, 1010 0100
(type = 2, value = 9, flags still 00), and wants to change type
to 1 (0b01). They shift 01 left by 6 to get 0100 0000 and OR it
straight into the word, the way Examples 1 and 2 did. What word do they
actually get, and what does the type field read as afterward? What
should they have done instead, and what word does the correct method
produce?
Show Answer Key
1. 0110 shifted left by 2 is 0001 1000. OR'd into an all-zero
word: word = 0001 1000 = 24.
2. Mask for value: 0011 1100. 1010 0110 AND 0011 1100 = 0010 0100. Shifted right by 2 (the field's starting bit): 0000 1001 = 9.
Converting to hex (Lesson 3's table): 1001 → 9, so 0x9.
3. Shifting 01 to bits 7-6 gives 0100 0000; OR-ing that straight
into 1010 0100 gives 1110 0100. Reading bits 7-6 back out of that
gives 11, not 1 — the old type value's leftover 1 bit at bit 7
merged with the new value instead of being replaced (10 OR 01 = 11),
the exact same bug as Example 3, now on the type field instead of
flags. The fix is the same: clear the field first. NOT-ing the type
mask 1100 0000 gives 0011 1111; AND-ing that into 1010 0100 clears
bits 7-6 to 0010 0100; OR-ing in the shifted new value 0100 0000
gives the correct result, 0110 0100. Reading bits 7-6 back out of that
gives 01, matching the intended new type value.
This is Part B's last lesson. Part C picks up a different problem entirely — how a word represents a negative number — starting from sign-magnitude's awkward downside.
Sign in to mark lessons complete and keep your progress.