Bit-level operations – bit flags and bit masks

This article introduces to bit flags, bit masks and basic bit-level operations on them. In the world of microcontrollers, when you need to save a boolean values (true/false) then it can be useful to “pack” individual boolean values into a single integer value for storage efficiency purposes. This is done by using the bitwise and shift operators to set, clear, and check individual bits in an integer, treating each bit as a separate boolean value. These individual bits are called bit flags. When talking about individual bits, we typically count from right to left, starting with leading “0” (zero). All examples presented here operate on integer type uint8_t (one byte, 8-bits; from stdint.h) which can be easly extended to other integer type.

Examples below have been compiled and tested using gnu-gcc.

$/> gcc example.c -o example

SET FLAG

To set a flag, we use bitwise OR equals operator (|=). It allows turning individual bits on.

pattern: [flags] |= [bitmask]

#include <stdint.h>
#include <assert.h>

#define FLAG_0 (1 << 0) // 1
#define FLAG_1 (1 << 1) // 2
#define FLAG_2 (1 << 2) // 4
#define FLAG_3 (1 << 3) // 8
#define FLAG_4 (1 << 4) // 16
#define FLAG_5 (1 << 5) // 32
#define FLAG_6 (1 << 6) // 64
#define FLAG_7 (1 << 7) // 128

int
main(void)
{
    uint8_t flags = 0b00000000; // initial value, no flags set

    /* Example 1: set first flag */
    flags |= FLAG_0;
    assert(flags == 0b00000001);

    /* Example 2: set second flag */
    flags |= FLAG_1;
    assert(flags == 0b00000011);

    /* Example 3: set next two flags */
    flags |= FLAG_3|FLAG_2;
    assert(flags == 0b00001111);

    return (0);
}

CLEAR FLAG

To clear a flag, we use bitwise AND equals operator (&=) with an inverse bit pattern (~) as a bitmask. It allows turning individual bits off.

patern: [flags] &= ~[bit pattern]

#include <stdint.h>
#include <assert.h>

#define FLAG_0 (1 << 0) // 1
#define FLAG_1 (1 << 1) // 2
#define FLAG_2 (1 << 2) // 4
#define FLAG_3 (1 << 3) // 8
#define FLAG_4 (1 << 4) // 16
#define FLAG_5 (1 << 5) // 32
#define FLAG_6 (1 << 6) // 64
#define FLAG_7 (1 << 7) // 128

int
main(void)
{
    uint8_t flags = 0b00001111; // initial value, four flags set

    /* Example 1: clear first flag */
    flags &= ~FLAG_0;
    assert(flags == 0b00001110);

    /* Example 2: clear second flag */
    flags &= ~FLAG_1;
    assert(flags == 0b00001100);

    /* Example 3: clear next two flags */
    flags &= ~(FLAG_3|FLAG_2);
    assert(flags == 0b00000000);

    return (0);
}

CHECK FLAG

To check a flag state, we use bitwise AND operator (&). It allows to determine if bit is on or off.

pattern: [flags] & [bitmask]

#include <stdint.h>
#include <assert.h>

#define FLAG_0 (1 << 0) // 1
#define FLAG_1 (1 << 1) // 2
#define FLAG_2 (1 << 2) // 4
#define FLAG_3 (1 << 3) // 8
#define FLAG_4 (1 << 4) // 16
#define FLAG_5 (1 << 5) // 32
#define FLAG_6 (1 << 6) // 64
#define FLAG_7 (1 << 7) // 128

int
main(void)
{
   uint8_t flags = 0b00001111; // initial value, four flags set

    /* Example 1: check first flag is set */
    assert((flags & FLAG_0) > 0); // (flags & FLAG_0) == 0b00000001

    /* Example 2: check second flag is set */
    assert((flags & FLAG_1) > 0); // (flags & FLAG_1) == 0b00000010

    /* Example 3: check next two flags are set */
    assert((flags & (FLAG_3|FLAG_2)) == 0b00001100);

    /* Example 4: check fourth or seventh flag is set */
    assert((flags & (FLAG_6|FLAG_3)) > 0);

    /* Example 5: check sixth flag is not set */
    assert((flags & FLAG_5) == 0);

    /* Example 6: check seventh and eight flags are not set */
    assert((flags & (FLAG_7|FLAG_6)) == 0);

    return (0);
}

TOGGLE FLAG

To toggle a flag state, we use bitwise XOR equals operator (^=). It allows flipping individual bits.

pattern: [flags] ^= [bitmask]

#include <stdint.h>
#include <assert.h>

#define FLAG_0 (1 << 0) // 1
#define FLAG_1 (1 << 1) // 2
#define FLAG_2 (1 << 2) // 4
#define FLAG_3 (1 << 3) // 8
#define FLAG_4 (1 << 4) // 16
#define FLAG_5 (1 << 5) // 32
#define FLAG_6 (1 << 6) // 64
#define FLAG_7 (1 << 7) // 128

int
main(void)
{
    uint8_t flags = 0b00000001; // initial value, one flag set

    /* Example 1: toggle first flag (which is already set) */
    flags ^= FLAG_0;
    assert(flags == 0b00000000);

    /* Example 2: toggle second flag (which is not set) */
    flags ^= FLAG_1;
    assert(flags == 0b00000010);

    /* Example 3: toggle two flags (second is already set) */
    flags ^= (FLAG_1|FLAG_0);
    assert(flags == 0b00000001);

    return (0);
}

USEFUL MACROS

#include <stdint.h>
#include <stdbool.h>
#include <assert.h>

#define FLAG_0         (1 << 0) // 1
#define FLAG_1         (1 << 1) // 2
#define FLAG_2         (1 << 2) // 4
#define FLAG_3         (1 << 3) // 8
#define FLAG_4         (1 << 4) // 16
#define FLAG_5         (1 << 5) // 32
#define FLAG_6         (1 << 6) // 64
#define FLAG_7         (1 << 7) // 128

#define SET_FLAG(n, f) ((n) |= (f)) 
#define CLR_FLAG(n, f) ((n) &= ~(f)) 
#define TGL_FLAG(n, f) ((n) ^= (f)) 
#define CHK_FLAG(n, f) ((n) & (f))

int
main(void)
{
    uint8_t flags = 0; // initial value 

    SET_FLAG(flags, FLAG_0);
    assert(CHK_FLAG(flags, FLAG_0) == true);

    CLR_FLAG(flags, FLAG_0);
    assert(CHK_FLAG(flags, FLAG_0) == false);

    TGL_FLAG(flags, FLAG_0);
    assert(CHK_FLAG(flags, FLAG_0) == true);

    return (0);
}

One thought on “Bit-level operations – bit flags and bit masks

Leave a Comment