Bit Manipulation

Bit Masks

A bit mask is essentially a number that contains information about which bits you care about. With a bit mask prepared, bitwise operations can then use it to select, set, clear, or test specific positions in a value.

The mask itself is just data. The operations do the work.

You see masks appear in almost any piece of bit level code. They keep visible the bits which they select, while letting whatever remains in the value remain unchanged.

The most common way to build a mask involves shifting a one into the desired bit position.

// Shift one into position to build a single-bit mask
unsigned int bit2{1u << 2};

The expression 1u << 2 results in a value that contains a single bit set only at position 2, with all other bits set to zero.

This single bit value can then be used alone or combined with other single bit values to create a larger mask.

// Set each bit that appears in either operand
unsigned int mask{(1u << 1) | (1u << 4)};

The bitwise ORing of several shifted values allows them all to be toggled on. Combining bits this way means that you'll have a mask where only the intended bits are turned on.

A binary literal can construct the same mask directly without needing any shifts.

// Use binary notation for this integer literal
unsigned int mask{0b0001'0010};

Both methods result in an identical value. The shift method is the most convenient when the position is computed, and a binary literal is convenient when the pattern must be evident immediately as it represents an obvious arrangement of fixed bits.

To test for bits, simply AND the values and check for nonzero.

unsigned int flags{0b0001'0110};
unsigned int mask{0b0001'0000};

// Test whether any bit selected by the mask is set
bool match{(flags & mask) != 0};

Any bit set in both the value and the mask will also be set in the result of the AND operation. Every other bit will be zeroed.

When a mask is composed of several bits, the nonzero test will succeed if any one of those bits is on.

To test if every bit named by a mask is on, compare the result of the AND operation with the mask itself.

// Check that every bit in the mask is present
bool all_present{(flags & mask) == mask};

If the two values are identical, then every bit represented by the mask is indeed turned on in the original value.

ORing with a mask forces every bit in the target to 1 where there is a 1 in the mask.

flags = flags | mask;

Bits in the value that are 1 where they correspond to a 0 in the mask will remain 1. Bits in the value that are 0 where they correspond to a 0 in the mask will also remain 0.

ANDing with an inverted mask will force every bit to 0 where there is a 1 in the mask.

// Clear the bits selected by this mask
flags = flags & ~mask;

The bitwise NOT operation on the mask first inverts every bit. ANDing the result will then set to 0 every bit in the target where there was a 1 in the original mask.

Sometimes a mask selects a relatively small group of bits that will need to be positioned as a smaller integer at the low positions.

Bitwise AND with a mask then isolates the bits one wants to extract.

Right bit shifting that result will then move those bits to be among the low positions.

unsigned int value{0b1101'0000};
unsigned int field_mask{0b0011'0000};
// Mask the field first, then shift it down to bit zero
unsigned int field{(value & field_mask) >> 4};

The extracted bits can then be treated as a small integer without regard for the other surrounding bits.

When a mask must be reused in several places, it can be handy to give it a name:

// Give important bit positions names instead of repeating raw shifts
const unsigned int READ_PERMISSION{1u << 0};
const unsigned int WRITE_PERMISSION{1u << 1};
const unsigned int EXEC_PERMISSION{1u << 2};

The operations that require the masks can be written like this, rather than repeating the shifts:

unsigned int permissions{
    READ_PERMISSION | WRITE_PERMISSION
};

If this layout is changed later, the user will not have to change multiple bits and hope they get it right.

Masks will always be the most readable when they consist of just a few bits. More complex bit manipulations should usually be achieved by composing several simple, readable masks.

A mask can be composed by ORing individual bits from specific bit positions.

Then, that mask can be used to set those positions in another value.

That mask can be used in an AND operation to test if any selected bit is set.

A complement mask allows those same bits to be zeroed out without affecting others.

A mask followed by a right shift operator extracts a small field of bits as a standard integer value.

Names are assigned to individual masks that can then be ORed together.

#include <iostream>

int main()
{
    unsigned int flags{0b0000'0000};
    std::cout << "Start: " << flags << '\n';

    unsigned int mask_a{(1u << 1) | (1u << 3)};
    flags = flags | mask_a;
    std::cout << "After setting bits 1 and 3: " << flags << '\n';

    bool has_bit3{(flags & (1u << 3)) != 0};
    std::cout << "Bit 3 present: " << has_bit3 << '\n';

    unsigned int clear_mask{1u << 1};
    // Invert every bit in this value
    flags = flags & ~clear_mask;
    std::cout << "After clearing bit 1: " << flags << '\n';

    unsigned int value{0b1110'0000};
    unsigned int field_mask{0b0110'0000};
    unsigned int field{(value & field_mask) >> 5};
    std::cout << "Extracted field: " << field << '\n';

    const unsigned int FLAG_X{1u << 0};
    const unsigned int FLAG_Y{1u << 2};

    unsigned int combined{FLAG_X | FLAG_Y};
    std::cout << "Combined named masks: " << combined << '\n';
}

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.