C/C++ Arena

Step 4 of 5

Flags

A common use of bits is flags: packing several yes/no settings into a single integer, one bit each. File permissions, network protocol headers, graphics options and hardware registers all work this way.

Give each flag its own bit with a shift:

#define BOLD      (1u << 0)   /* 001 */
#define ITALIC    (1u << 1)   /* 010 */
#define UNDERLINE (1u << 2)   /* 100 */

Then four operations cover everything:

Goal Code
turn a flag on style |= ITALIC;
turn a flag off style &= ~ITALIC;
toggle a flag style ^= ITALIC;
test a flag if (style & ITALIC)
#include <stdio.h>

#define BOLD      (1u << 0)
#define ITALIC    (1u << 1)
#define UNDERLINE (1u << 2)

int main(void) {
    unsigned style = BOLD | UNDERLINE;
    printf("%u\n", style);
    style ^= BOLD;
    style |= ITALIC;
    printf("%u bold=%d italic=%d\n", style, (style & BOLD) != 0, (style & ITALIC) != 0);
    return 0;
}
5
6 bold=0 italic=1

Why &= ~FLAG turns a flag off

~ITALIC has every bit set except the italic bit (...11101). AND-ing with it keeps every other bit as it was and forces the italic bit to 0.

Why != 0 when testing

style & ITALIC is either 0 or the flag's value (here 2), not 1. As a condition in if that's fine, since non-zero is true. When you want an actual 0 or 1, compare with != 0 (or use !!).

Combining flags with | when calling a function (like BOLD | ITALIC) is a pattern you'll see in many C APIs, including open() on Unix.

Bit-fields

C can also pack small fields into an integer for you, with bit-fields:

struct Style {
    unsigned bold : 1;      /* 1 bit: 0 or 1 */
    unsigned italic : 1;
    unsigned size : 6;      /* 6 bits: 0 to 63 */
};

Each member uses only the number of bits after the colon, and you read and write it like any member (s.size = 12;). That saves memory in big arrays of small records. But the compiler chooses the exact layout (which end the bits start from, padding), so bit-fields are not a portable way to match a file format or a hardware register. Use masks and shifts, as above, for that.

Byte order

A 4-byte int like 0x12345678 is stored as 4 separate bytes, and machines disagree about their order. Little-endian machines, which include x86 and ARM as it's almost always configured (so nearly every PC and phone, and WebAssembly), store the lowest byte first: 78 56 34 12. Big-endian machines store 12 34 56 78, and network protocols traditionally send numbers in big-endian order. Shifts and masks work on values, so (x >> 8) & 0xFF gives the same answer everywhere. Byte order only matters when you look at memory one byte at a time, or read and write raw bytes, as binary files do in the next module.

Your turn: fill in the operators.

Previous: Bitwise operators Next: Challenge: power of two