Embercode
✕
C++ OperatorsLesson 9 of 14

50 min read

Bit masks

Why this matters

Many settings are yes or no answers. A file is readable or not, writable or not, runnable or not. A program could store each answer in its own variable, but that takes one variable per answer. Storing all of them in one integer, with one bit per answer, takes one variable for the whole set. Linux file permissions work this way. The chmod command sets three bits for the owner, three for the group and three for everyone else, and the system checks those bits when a user tries to read, write or run a file. Many hardware and network settings use the same method.

A bit is one binary digit, 0 or 1. A bit mask, usually shortened to mask, is an integer used to pick out which bits an operation should look at. You combine a mask with a value using the bit operators. The operators themselves are explained in the earlier lesson on bitwise operators and precedence. This lesson applies them to four common jobs: set a bit, clear a bit, toggle a bit and test a bit.

Bit positions and the mask for one bit

Bit positions are counted from 0, starting at the right. Position 0 is worth 1, position 1 is worth 2, position 2 is worth 4, and so on. A mask with exactly one bit on is written 1u << k, where k is the position you want. The expression 1u << 2 shifts the unsigned number 1 two places to the left, which gives 4.

The u suffix makes the number unsigned. For an unsigned value, every bit is a plain 0 or 1 and none of them acts as a minus sign. This matters when you print a mask or invert it with ~, as the last section shows.

The table below lists some masks for an 8-bit view of the number.

k    1u << k    binary (8 bits)
0    1          00000001
1    2          00000010
2    4          00000100
3    8          00001000
4    16         00010000

Keep k between 0 and 31 for a 32-bit unsigned value. A shift by 32 or more on a 32-bit value is undefined behaviour, which is the C++ term for "the language does not say what happens, so the program can do anything."

To print a value as bits, this lesson uses std::bitset. It is a standard library class that stores a fixed number of bits and prints them as a row of 0s and 1s. bitset<4> shows 4 bits, bitset<8> shows 8 bits, and so on. The program below needs #include <bitset>.

Worked example 1: set, clear, toggle and test

This program starts with a value of 0110, which has bits 1 and 2 on. It then applies each operation in turn, and prints the value after each step.

C++ · editable

Output:

0110
0111
0101
1101
1
0
4

Each operation works on one bit and leaves the others alone. The sections below explain how each one works.

Setting a bit with OR

To set bit k, use x | (1u << k). The OR operator gives 1 when either input bit is 1. Where the mask has a 1, the result is 1 whatever x held. Where the mask has a 0, OR leaves x unchanged. So only bit k is forced on.

In the program, x | (1u << 0) turns 0110 into 0111. Bit 0 was 0 and is now 1. Bits 1 and 2 stay on, because the mask has 0 in those positions.

Clearing a bit with AND and NOT

To clear bit k, use x & ~(1u << k). The ~ operator flips every bit of the mask. So the mask 1u << 1, which is 0010, becomes 1101 in the four bits that matter, with every other position also set to 1. AND gives 0 when either input bit is 0. Where the mask has a 0 at position k, the result is 0. Where the mask has a 1, AND keeps the bit of x. So only bit k is cleared.

In the program, x & ~(1u << 1) turns 0111 into 0101. Bit 1 was 1 and is now 0. Bits 0 and 2 stay on.

Toggling a bit with XOR

To toggle bit k, use x ^ (1u << k). XOR gives 1 when the two input bits differ. Where the mask has a 1, the bit of x flips. Where the mask has a 0, the bit stays the same. Applying the same toggle twice gives back the original value, because each flip undoes the other.

In the program, x ^ (1u << 3) turns 0101 into 1101. Bit 3 was 0 and is now 1.

Testing a bit with AND

To test bit k, use (x & (1u << k)) != 0. The AND keeps only bit k of x. All other bits become 0. The result is 0 when bit k is off, and it is the mask value when bit k is on. Comparing with != 0 turns that result into a plain yes or no.

In the program, bit 2 of 1101 is on, so the first test prints 1. Bit 1 is off, so the second test prints 0. The last line prints 4, not 1, because x & (1u << 2) returns the mask value itself. This is covered in the mistakes section.

Worked example 2: several bits at once

A mask can have more than one bit on. This is useful for checks such as "is READ and WRITE both allowed?" The next program names three permission bits and tests them together.

C++ · editable

Output:

101
0 1
000

The value READ | EXEC has bits 2 and 0 on, so it prints 101. The first test asks whether both READ and WRITE are on. The mask READ | WRITE is 110, and perms & (READ | WRITE) is 100. That does not equal 110, so the answer is 0. The second test asks whether READ is on and WRITE is off. The masked value is 100, which equals READ, so the answer is 1.

The last line clears two bits at once. The mask READ | EXEC is 101, and ~ of that flips it to 010. ANDing perms with 010 keeps only the WRITE position, which is already 0, so the result is 000.

The parentheses around (READ | WRITE) are needed. Without them, & binds more tightly than |, so perms & READ | WRITE means (perms & READ) | WRITE. With perms equal to 101, that gives 110 instead of 100.

Worked example 3: packing a colour into one number

Bit masks are also used to store several small numbers in one integer. A colour is often stored as three numbers from 0 to 255, one each for red, green and blue. Each of those fits in 8 bits, so all three fit in 24 bits of one 32-bit integer. Red takes bits 16 to 23, green takes bits 8 to 15, and blue takes bits 0 to 7.

To pack the values, shift each one into its place and combine them with OR. To unpack a value, shift it down to position 0 and then AND with 0xFF, which is the mask with the lowest 8 bits on. uint8_t and uint32_t are standard names for unsigned integers that are exactly 8 and 32 bits wide.

C++ · editable

Output:

packed: 16744448
hex: 0xff8000
red: 255
green: 128
blue: 0
green cleared: 0
bits of blue: 00000000

The packed value is 255 shifted left 16 places, plus 128 shifted left 8 places, plus 0. That is 16711680 plus 32768, which is 16744448. The hex manipulator (a setting that changes how cout prints numbers) prints the same number in base 16, and dec switches the output back to base 10. The static_cast<uint32_t> makes each shift happen on an unsigned 32-bit value. Without it, the uint8_t argument would be promoted to a signed int before the shift. For these values the printed result would be the same, but unsigned is the type to use for bit work.

The greenOff line clears the green field. The mask 0xFFu << 8 covers bits 8 to 15. Its inverse is ANDed with the colour, so bits 8 to 15 become 0 and the other fields stay as they were.

Naming masks

A mask such as 1u << 2 is hard to read in the middle of a long expression. Give each mask a name with constexpr, which tells the compiler the value is fixed at compile time. Then the code reads as a list of flags, and a change to one position needs only one edit.

C++ · editable

Output:

101
111
011
1

The first line sets VISIBLE and LOCKED. The second line toggles HIDDEN on. The third line clears LOCKED, and the last line tests HIDDEN. Each line reads as the action it performs, which is the main benefit of naming masks.

Common mistakes

Forgetting that ~ binds more tightly than <<

The operator ~ is a unary operator, which means it takes one operand. It binds more tightly than any binary operator, including <<. So ~1u << 2 means (~1u) << 2, and not ~(1u << 2). The next program shows the difference.

C++ · editable

Output:

00001000
00001011

The first version flips every bit of 1 first, then shifts. That clears bits 0, 1 and 2 of x, when only bit 2 was meant to be cleared. The second version clears only bit 2, which is the intended result.

Fix: put the mask in parentheses, as in ~(1u << k).

Printing a test result instead of a yes or no

The expression x & (1u << k) does not return 1 when the bit is on. It returns the mask value, which is 1u << k, so bit 2 gives 4 and bit 5 gives 32. Printing that value directly makes it look as if the test failed. The first worked example prints 4 for this reason.

Fix: compare with 0, as in (x & (1u << k)) != 0, or write bool around the expression.

Using == without parentheses

The comparison == binds more tightly than &. So x & 4 == 4 does not test bit 2. It means x & (4 == 4), which is x & 1, which tests bit 0. The next snippet shows the result for x set to 4.

C++ · editable

Output:

bit 2 is off
bit 2 is on

The first condition is false even though bit 2 is on. The second condition, with the parentheses, is true.

Using a signed mask and printing it

A plain int mask behaves differently from an unsigned one when you invert it. The expression ~(1 << 3) on an int gives -9 on a typical system, because every bit above position 3 is set and the top bit makes the value negative. The bit operators still work on a negative value, and the AND still clears bit 3. But the printed value is hard to read. Use unsigned for masks you intend to print or to compare.

Practice

Exercise 1. Write an expression that sets bit 5 of the unsigned variable x, and leaves the other bits as they were.

Show the answer

x = x | (1u << 5);. The mask has bit 5 on, so OR forces that bit to 1. Every other bit of the mask is 0, so OR leaves those bits unchanged. For x equal to 0b0101, the result is 0b100101.

Exercise 2. Write an expression that clears bit 0 of the unsigned variable x, and leaves the other bits as they were.

Show the answer

x = x & ~1u;. The mask ~1u has every bit on except bit 0. AND keeps every bit of x except bit 0, which becomes 0. For x equal to 0b0101, the result is 0b0100.

Exercise 3. Write an expression that gives 1 when bit k of x is on and 0 when it is off. Use the shift and AND operators.

Show the answer

(x >> k) & 1. The shift moves bit k down to position 0. The AND with 1 keeps only that bit. For x equal to 0b0101 and k equal to 2, the result is 1. For k equal to 1, the result is 0.

Exercise 4. Write an expression that flips bits 0 and 1 of x together, and leaves the other bits alone.

Show the answer

x = x ^ 0b11;. The mask 0b11 has bits 0 and 1 on, so XOR flips exactly those two bits. For x equal to 0b0101, the result is 0b0110.

Exercise 5. Write a condition that is true only when bits 1 and 3 of x are both on.

Show the answer

(x & 0b1010) == 0b1010. The mask 0b1010 has bits 1 and 3 on. The AND keeps only those two bits, and the comparison is true only when both are still on. The parentheses are needed, because == binds more tightly than &. For x equal to 0b1010, the condition is true. For x equal to 0b0010, it is false.

Summary

  • A bit mask is an integer used to choose which bits an operation touches. 1u << k is the mask for bit k.
  • To set a bit, OR with the mask. To clear a bit, AND with the inverted mask, written ~(1u << k). To toggle a bit, XOR with the mask.
  • To test a bit, AND with the mask and compare the result with 0, because the AND returns the mask value and not a yes or no.
  • Use unsigned masks, and put every mask in parentheses before you use ~ or == with it.
  • Several bits can be set, cleared or tested together by building one mask with OR.
  • Packing several small numbers into one integer uses shifts to place each field and AND to read it back.
Practice this topic: coding problems for C++ Operators →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Precedence trees