Bitwise Operations in C: Operators, Masks and Worked Examples

Learn C bitwise operators by tracing one unsigned example, then use the same ideas for flags, packed fields, power-of-two checks and set-bit counting.

KnowledgeGate Team

Exam prep & CS education

Updated 8 Sep 20265 min read

The symbols &, | and ^ become predictable when operands are aligned bit by bit, and & is easy to confuse with &&. Run a complete C program with 44 and 23, then use flags, packed fields and fixed-answer exercises. Displays show the low 8 bits, while the objects are unsigned int.

What bitwise operations do to individual bits

A bitwise operation applies a rule at each aligned position. Use 44u = 00101100 and 23u = 00010111, shown as low-byte 8-bit views. Review Number Systems and Base Conversions Explained if needed.

Operator

Name

Rule

&

AND

The result bit is 1 only for 1 & 1

|

OR

The result bit is 1 when either input is 1

^

XOR

The result bit is 1 when the inputs differ

~

Complement

Flips every bit

<<

Left shift

Moves bits left

>>

Right shift

Moves bits right

The one-bit truth table makes the first three rules precise:

A

B

A AND B

A OR B

A XOR B

0

0

0

0

0

0

1

0

1

1

1

0

0

1

1

1

1

1

1

0

Bitwise operators transform patterns; logical operators test truth. 6u & 3u gives 110 & 011 = 010, or 2. 6 && 3 sees two nonzero operands and gives 1. See C Programming & Data Structures for the wider C foundation.

Work every operator by hand with 44 and 23

Align the low 8 bits and apply one rule to each column:

  x       00101100  (44)
  y       00010111  (23)
x & y     00000100  (4)
x | y     00111111  (63)
x ^ y     00111011  (59)

AND keeps bit 2 because both inputs contain 1. OR sets bit 5 because 1 | 0 = 1. XOR sets bit 0 because 0 ^ 1 = 1.

(~44u) & 0xFFu = 11010011 = 211; the mask selects the low byte, while plain ~44u covers the full unsigned int. Also, 44u << 2 = 10110000 = 176 and 44u >> 2 = 00001011 = 11. No set bit is discarded beyond this view.

Bit-by-bit results of AND, OR, XOR, complement and shifts for x = 44 and y = 23 shown as 8-bit rows.

Run the complete C program and match every output

Standard C has no portable %b, so use a helper:

#include <stdio.h>

static void print8(unsigned value) {
    for (int bit = 7; bit >= 0; --bit) {
        putchar((value & (1u << bit)) ? '1' : '0');
    }
}

static void show(const char *label, unsigned value) {
    printf("%-8s = ", label);
    print8(value & 0xFFu);
    printf(" (%u)\n", value & 0xFFu);
}

int main(void) {
    unsigned x = 44u;
    unsigned y = 23u;

    show("x", x);
    show("y", y);
    show("x & y", x & y);
    show("x | y", x | y);
    show("x ^ y", x ^ y);
    show("~x", (~x) & 0xFFu);
    show("x << 2", x << 2);
    show("x >> 2", x >> 2);
    return 0;
}

Compile with cc -std=c17 -Wall -Wextra bitwise.c -o bitwise, then run ./bitwise:

x        = 00101100 (44)
y        = 00010111 (23)
x & y    = 00000100 (4)
x | y    = 00111111 (63)
x ^ y    = 00111011 (59)
~x       = 11010011 (211)
x << 2   = 10110000 (176)
x >> 2   = 00001011 (11)

The u suffix makes operands unsigned. value & 0xFFu limits display to positions 7 through 0, not the width of unsigned int.

Use masks to set, clear, toggle and test flags

Use four permission flags, displayed as SHARE EXECUTE WRITE READ.

#define READ    (1u << 0)
#define WRITE   (1u << 1)
#define EXECUTE (1u << 2)
#define SHARE   (1u << 3)

From flags = 0000, flags |= READ | WRITE gives 0011 (3). (flags & WRITE) != 0u is true because the mask returns 0010 (2). Next, flags ^= WRITE gives 0001 (1), flags |= EXECUTE gives 0101 (5), and flags &= ~READ gives 0100 (4).

OR sets selected bits, AND with a complemented mask clears them, XOR toggles them, and AND without assignment tests them.

Pack and extract three fields in one byte

Bits 0 to 2 store priority (0 to 7), bits 3 to 4 store mode (0 to 3), bit 5 stores enabled, and bits 6 to 7 are unused. Pack 5, 2 and 1 as:

unsigned packed = (5u & 0x7u)
                | ((2u & 0x3u) << 3)
                | ((1u & 0x1u) << 5);

The contributions are 00000101 (5), 00010000 (16) and 00100000 (32). OR produces 00110101 = 0x35 = 53. Extract with packed & 0x7u = 5, (packed >> 3) & 0x3u = 2, and (packed >> 5) & 0x1u = 1.

The 8-bit byte 0x35 split into priority, mode, enabled and unused fields with pack and extract steps.

Apply two useful bit patterns and trace them

For unsigned n, test a positive power of two with n != 0u && (n & (n - 1u)) == 0u. For 32, 00100000 & 00011111 = 00000000, true. For 40, 00101000 & 00100111 = 00100000 (32), false. Subtracting one flips the lowest set bit and all lower bits.

The same observation counts set bits efficiently:

while (value != 0u) {
    value &= value - 1u;
    ++count;
}

For 44: 44 (00101100) -> 40 (00101000) -> 32 (00100000) -> 0 (00000000). Three iterations mean three set bits. Also, 0xB4u & 0x0Fu = 0x04u, so the low nibble is 4.

Avoid precedence, width and signed-shift traps

  • Do not confuse 6u & 3u = 2 with 6 && 3 = 1.

  • Parenthesise before comparison. For flags = 0u and mask = 4u, (flags & mask) == 0u is true. flags & mask == 0u parses as flags & (mask == 0u) and gives 0.

  • Do not treat ~ as an eight-bit operator. Only (~44u) & 0xFFu gives the fixed low-byte result 211.

  • Keep masks and shifts unsigned. An unrepresentable signed left shift is undefined, while right-shifting a negative signed value is implementation-defined.

  • Never use a negative shift count or one greater than or equal to the promoted left operand's width. Such a count is undefined.

  • Remember that standard C does not define %b; the helper above is intentional.

Practise the question forms, then take the next step

Write aligned binary rows before checking these answers:

  1. 0xB4u & 0x0Fu gives 0x04u (4).

  2. 0xB4u | 0x02u sets bit 1 and gives 0xB6u (182).

  3. 0xB4u ^ 0x80u toggles bit 7 and gives 0x34u (52).

  4. (0x35u >> 3) & 0x3u extracts mode and gives 2.

Also practise choosing masks, repairing precedence bugs, tracing shifts, extracting fields, checking powers of two and counting set bits.

The short version is: AND selects, OR sets, XOR toggles, complement flips, and shifts reposition. Masks make each operation selective. Continue with the C Language Course: Concepts, MCQs & Coding, try the focused C Programming Course, or compare broader paths in Coding & Skills.