← Binary Course — Part B, lesson 3 of 4 (Bitwise operations)
7. Masking: Reading and Writing Just the Bits You Care About
The concept
Every AND/OR/XOR/NOT/shift instruction from Lessons 5 and 6 takes a whole word as input and hands back a whole word as output — there's no instruction that means "give me bit 5 by itself," the way an array lets a program address one element on its own. That's not a shortcoming of the paper diagrams used to work these out by hand. A grid like
A: 0 1 1 0
B: 0 0 0 1
A AND B: 0 0 0 0
is a faithful picture of what's physically happening — real hardware
typically computes AND/OR/XOR with one logic gate per bit position, all of
them wired to fire at the same time. The bits are genuinely there, laid
out side by side, exactly like the grid suggests. What's missing is a way
for an instruction to name one of those positions independently. A word
is the smallest unit the instruction set lets a program address; the bits
inside it are real, just not individually reachable — 0110 and 0x6
are two notations for the identical word (Lesson 3), and neither one lets
an instruction pick out "just the third bit" as its own separate thing to
operate on.
Masking is how a word-oriented instruction set fakes bit-level access anyway. An instruction can't hand back bit 5 in isolation, but it can hand back a whole word whose only meaningful bit happens to be bit 5 — which is exactly what a mask is built to arrange.
The last two lessons introduced five tools — AND, OR, XOR, NOT, and shifts — mostly one at a time. This lesson doesn't add a new tool. It's about combining those five to do something genuinely useful: working with one narrow slice of a word (a single flag bit, or a small multi-bit field) without disturbing anything else living in the same word.
A mask is a bit pattern chosen for exactly one purpose: to pick out which positions an operation should touch and which it should leave alone. Which of the five tools you reach for depends on what "touch" means:
- Read a field that starts at bit 0. AND with a mask that has
1s over the field and0s everywhere else. Every position under a mask1passes through unchanged (x AND 1 = x); every position under a mask0is forced to0(x AND 0 = 0, regardless ofx). The rest of the word simply vanishes from the result. - Read a field that doesn't start at bit 0. Same AND as above, then shift right by the field's starting bit index, to bring the field's own bit 0 down to the real bit 0 so it reads as an ordinary number.
- Test whether one bit is set, without caring about its numeric
position. AND with a mask that has a single
1, built by shifting1left to that bit's index. No need to shift the result back down — zero-vs-nonzero is the whole answer. - Set specific bits to 1 without touching the rest. OR with a mask
that has
1s at the positions to set,0s elsewhere — Lesson 5'sx OR 0 = xis exactly what protects the untouched positions. - Clear specific bits to 0 without touching the rest. Start with a
mask that has
1s at the positions to clear — the same shape as the "test" mask above — then run it through NOT, which flips it to0s at those positions and1s everywhere else. AND the word with that flipped mask: the0s force the target positions to0, and the1s elsewhere let every other bit pass through unchanged.
Nothing forces the clear recipe to go through NOT — a mask with 0s at
the target positions and 1s elsewhere could just as well be written down
directly. The reason to build it from the "care about" shape instead is
what has to be remembered. Read, test, and set all use the exact same
mask for a given field: 1s where that field lives, 0s elsewhere. If
clearing used its own separately-written mirror-image pattern, every field
would need two versions of its mask kept in sync by hand — one shape and
its exact opposite — with every field-layout change risking one getting
updated and the other forgotten. Deriving the clear mask from the same
"care about" shape with NOT means each field only ever needs one mask,
period; NOT is just how that one mask gets translated into the form AND
needs for clearing. This starts to matter for real once a word holds
several packed fields side by side, each with its own mask — see Lesson 8.
Every one of these reuses a rule already proven in Lessons 5 and 6; this lesson is just naming the five recipes built from them.
Example 1 — Reading a field at the bottom of the word
Take W = 0110 1101 (109, the running example since Lesson 2) and read
just its low nibble:
mask: 0000 1111
W: 0110 1101
W AND mask: 0000 1101
The mask's 0s over the top nibble force those four positions to 0; the
mask's 1s over the bottom nibble let W's own bits through untouched.
W AND mask = 0000 1101 = 0xd (Lesson 3's nibble table), 13.
Example 2 — Reading a field that isn't at the bottom
Say bits 5-4 of W hold a 2-bit field, and the goal is that field's plain
numeric value. Mask first, then shift the result down to bit 0:
mask: 0011 0000
W: 0110 1101
W AND mask: 0010 0000
(>> 4 to normalize): 0000 0010
Masking alone leaves the field sitting at bits 5-4 — correct as a bit
pattern, but 0010 0000 is 32, not the field's actual 2-bit value.
Shifting right by 4 (the field's own starting position) brings it down to
bit 0: 0000 0010 = 2, the field's real value.
Example 3 — Testing a single bit
To check whether bit 2 of W is set, build a one-bit mask by shifting:
1 << 2 = 0000 0100.
mask: 0000 0100
W: 0110 1101
W AND mask: 0000 0100
The result is nonzero, so bit 2 was 1. There's no need to shift this
result down to bit 0 — unlike Example 2, nothing here needs to be read as
a number; zero-vs-nonzero already answers the yes/no question.
Example 4 — Setting a bit
To set bit 1 of W to 1 without disturbing any other bit, OR with a
mask that has only bit 1 set:
mask: 0000 0010
W: 0110 1101
W OR mask: 0110 1111
Bit 1 was 0 and becomes 1. Every other position ORs against the mask's
0, which Lesson 5 already showed leaves it exactly as it was.
W OR mask = 0110 1111 = 111.
Example 5 — Clearing a bit
To clear bit 3 of W back to 0 without disturbing anything else, don't
AND with a mask that has a 1 at bit 3 — that would extract bit 3, not
clear it (Example 3's move, not this one). Instead, build a mask with a
0 only at bit 3, by taking 0000 1000 and running it through NOT:
starting mask: 0000 1000
NOT mask: 1111 0111
W: 0110 1101
W AND NOT mask: 0110 0101
Bit 3's 0 in the NOT'd mask forces that position to 0; every other
position ANDs against a 1 and passes through unchanged.
W AND NOT mask = 0110 0101 = 101.
Exercises
Exercise 1. W2 = 1011 0110 stores a 3-bit field at bits 6-4. Using
Example 2's mask-then-shift method, what is that field's plain numeric
value?
Exercise 2. Starting again from W = 0110 1101, set bits 1 and 0 to
1 in a single OR (one mask covering both positions). Give the result in
both binary and hex (Lesson 3's nibble table).
Exercise 3. A classmate wants to clear bit 3 of W = 0110 1101 — the
same goal as Example 5. Instead of NOT-ing the mask first, they AND W
directly with 0000 1000 (a 1 only at bit 3) and report the result as
the answer. What do they actually get, and what operation does that
0000 1000 mask correctly perform instead? What should they have done?
Exercise 4. Toggle bit 2 of W = 0110 1101 by XOR-ing with
0000 0100. Then XOR that result with the same mask a second time. What
do you get after each step, and why does the second XOR land exactly back
on the original W — always, with no exceptions? (Compare this to Lesson
6's Exercise 3, where a left-shift-then-right-shift round trip only
sometimes recovered the original value.)
Show Answer Key
1. Mask bits 6-4: 0111 0000. W2 AND mask = 0011 0000. Shifted
right by 4 (the field's starting bit): 0000 0011 = 3. The field's value
is 3.
2. Mask covering bits 1 and 0: 0000 0011. W OR mask = 0110 1111.
Converting each nibble with Lesson 3's table: 0110 → 6, 1111 → f.
Result: 0110 1111 = 0x6f.
3. W AND 0000 1000 = 0000 1000 = 8 — not 0. That's because W's bit
3 actually was 1: AND-ing with a mask that has a 1 only at bit 3
forces every other position to 0 and lets bit 3 through unchanged,
which extracts (tests) bit 3 rather than clearing it — Example 3's
operation, not Example 5's. To actually clear bit 3, the mask needs a 0
at bit 3 and 1s everywhere else, which means running 0000 1000 through
NOT first (1111 0111) before AND-ing: W AND 1111 0111 = 0110 0101 =
101, matching Example 5.
4. W XOR 0000 0100 = 0110 1001 = 105 (bit 2 flips from 1 to 0).
XOR-ing that same mask again gives 0110 1101 = 109, exactly W. This
always works, with no exceptions, because XOR never discards information
the way an overflowing shift does: x XOR mask XOR mask always simplifies
back to x, since XOR-ing the same value twice at any position cancels
out (0 XOR 1 XOR 1 = 0, 1 XOR 1 XOR 1 = 1 — the second XOR always
undoes the first). Lesson 6's shift round trip only worked when nothing
overflowed off the top; toggling with XOR has no equivalent failure case,
because nothing is ever dropped — every bit stays right where it started,
just flipped and flipped back.
Sign in to mark lessons complete and keep your progress.