Bit Manipulation in C – Set, Clear, Toggle, and Count Bits

Bit manipulation in C means reading, setting, clearing, and toggling individual bits within an integer. At the hardware level, everything from GPIO port states to network protocol flags to image pixel data is ultimately a pattern of bits. Knowing how to work with them efficiently — using only bitwise operators with no branches — is an essential skill for systems, embedded, and competitive programming.

The Six Bitwise Operators

Operator Symbol Action Example (8-bit)
AND & 1 only when both bits are 1 0b1100 & 0b1010 = 0b1000
OR | 1 when either bit is 1 0b1100 | 0b1010 = 0b1110
XOR ^ 1 when bits differ 0b1100 ^ 0b1010 = 0b0110
NOT ~ Flip all bits ~0b00001100 = 0b11110011
Left shift << Shift bits left, fill 0 from right 0b0001 << 3 = 0b1000
Right shift >> Shift bits right (arithmetic/logical) 0b1000 >> 2 = 0b0010

Always use unsigned integers for bit manipulation. Right-shifting a signed negative number is implementation-defined in C. Left-shifting into the sign bit causes undefined behaviour. Using unsigned int avoids both problems.

The Core Bit Manipulation Patterns

Four operations — set, clear, toggle, check — cover nearly every bit manipulation task. Each takes a value x and a bit position n (0 = least-significant bit):

Pattern              Formula                   Mnemonic
──────────────────────────────────────────────────────────
Set bit n            x | (1u << n)             OR with a 1 turns it on
Clear bit n          x & ~(1u << n)            AND with a 0 forces it off
Toggle bit n         x ^ (1u << n)             XOR flips whatever is there
Check bit n          (x >> n) & 1u             shift to LSB, mask with 1

Bit Manipulation Program in C

#include <stdio.h>

/* Print the low 8 bits of x in binary */
static void print_bits(unsigned int x)
{
    int i;
    for (i = 7; i >= 0; i--)
        putchar(((x >> i) & 1u) ? '1' : '0');
}

/* ── Core four ───────────────────────────────────────────── */
unsigned int set_bit   (unsigned int x, int n) { return x |  (1u << n); }
unsigned int clear_bit (unsigned int x, int n) { return x & ~(1u << n); }
unsigned int toggle_bit(unsigned int x, int n) { return x ^  (1u << n); }
int          check_bit (unsigned int x, int n) { return (x >> n) & 1u;  }

/* ── Count set bits — Brian Kernighan's algorithm ─────────── */
/* Each iteration clears the lowest set bit: x &= (x-1)        */
int count_set_bits(unsigned int x)
{
    int count = 0;
    while (x) {
        x &= x - 1u;   /* clears lowest set bit */
        count++;
    }
    return count;
}

/* ── Check if x is a power of 2 ────────────────────────────── */
/* A power of 2 has exactly one set bit.                        */
/* x-1 flips all bits below that bit, so x & (x-1) == 0.       */
int is_power_of_two(unsigned int x)
{
    return x && !(x & (x - 1u));
}

/* ── Isolate the lowest set bit ────────────────────────────── */
/* Two's complement: -x flips all bits and adds 1.              */
/* x & (-x) keeps only the lowest set bit.                      */
unsigned int lowest_set_bit(unsigned int x)
{
    return x & (unsigned int)(-(int)x);
}

/* ── Swap two integers without a temp variable ─────────────── */
/* Only safe when a and b are distinct memory locations.         */
void xor_swap(int *a, int *b)
{
    if (a != b) {
        *a ^= *b;
        *b ^= *a;
        *a ^= *b;
    }
}

/* ── Extract n bits starting at position p ─────────────────── */
/* Shift right to align, then mask with n ones.                  */
unsigned int extract_bits(unsigned int x, int p, int n)
{
    return (x >> p) & ~(~0u << n);
}

/* ── Reverse the bits of an 8-bit value ────────────────────── */
unsigned int reverse_bits_8(unsigned int x)
{
    unsigned int r = 0;
    int i;
    for (i = 0; i < 8; i++) {
        r = (r << 1) | (x & 1u);
        x >>= 1;
    }
    return r & 0xFF;
}

int main(void)
{
    unsigned int x = 0x2C;   /* 0b00101100 = 44 decimal */
    int a = 15, b = 27;

    printf("x = 0x%02X = ", x); print_bits(x); printf(" (%u)\n\n", x);

    /* Set, clear, toggle, check */
    printf("set_bit(x, 0)    = "); print_bits(set_bit(x, 0));
    printf(" (bit 0 set)\n");

    printf("clear_bit(x, 3)  = "); print_bits(clear_bit(x, 3));
    printf(" (bit 3 cleared)\n");

    printf("toggle_bit(x, 5) = "); print_bits(toggle_bit(x, 5));
    printf(" (bit 5 toggled)\n");

    printf("check_bit(x, 2)  = %d  (bit 2 is %s)\n\n",
           check_bit(x, 2), check_bit(x, 2) ? "set" : "clear");

    /* Count set bits */
    printf("count_set_bits(0x2C) = %d\n", count_set_bits(x));
    printf("count_set_bits(0xFF) = %d\n\n", count_set_bits(0xFF));

    /* Power of two */
    printf("is_power_of_two(16) = %d\n", is_power_of_two(16));
    printf("is_power_of_two(18) = %d\n\n", is_power_of_two(18));

    /* Lowest set bit */
    printf("x = 0b00101100, lowest_set_bit = ");
    print_bits(lowest_set_bit(x)); printf("\n\n");

    /* XOR swap */
    printf("Before swap: a=%d, b=%d\n", a, b);
    xor_swap(&a, &b);
    printf("After  swap: a=%d, b=%d\n\n", a, b);

    /* Extract bits */
    printf("extract_bits(0x2C, p=2, n=3) = %u  (3 bits from pos 2)\n\n",
           extract_bits(x, 2, 3));

    /* Reverse bits */
    printf("reverse_bits_8(0b00101100) = ");
    print_bits(reverse_bits_8(x)); printf("\n");

    return 0;
}

How to Compile and Run

gcc -ansi -Wall -Wextra -o bitmani bitmani.c
./bitmani

Sample Output

x = 0x2C = 00101100 (44)

set_bit(x, 0)    = 00101101 (bit 0 set)
clear_bit(x, 3)  = 00100100 (bit 3 cleared)
toggle_bit(x, 5) = 00001100 (bit 5 toggled)
check_bit(x, 2)  = 1  (bit 2 is set)

count_set_bits(0x2C) = 3
count_set_bits(0xFF) = 8

is_power_of_two(16) = 1
is_power_of_two(18) = 0

x = 0b00101100, lowest_set_bit = 00000100

Before swap: a=15, b=27
After  swap: a=27, b=15

extract_bits(0x2C, p=2, n=3) = 3  (3 bits from pos 2)

reverse_bits_8(0b00101100) = 00110100

Visual Trace: Why x &= (x-1) Counts Bits

x = 0b00101100  (3 set bits)

Iteration 1: x - 1 = 0b00101011
             x & (x-1) = 0b00101100 & 0b00101011 = 0b00101000  count=1

Iteration 2: x - 1 = 0b00100111
             x & (x-1) = 0b00101000 & 0b00100111 = 0b00100000  count=2

Iteration 3: x - 1 = 0b00011111
             x & (x-1) = 0b00100000 & 0b00011111 = 0b00000000  count=3

x == 0, loop ends. Answer: 3 ✓

Each iteration strips exactly the lowest set bit, so the loop runs exactly as many times as there are set bits — even for sparse numbers like 0x80000000 (one iteration instead of 32).

Visual Trace: Why x & (x-1) == 0 Detects Powers of Two

x = 16 = 0b00010000  (power of 2 — exactly one set bit)
x - 1 = 15 = 0b00001111
x & (x-1) = 0b00010000 & 0b00001111 = 0b00000000  → IS a power of 2 ✓

x = 18 = 0b00010010  (not a power of 2 — two set bits)
x - 1 = 17 = 0b00010001
x & (x-1) = 0b00010010 & 0b00010001 = 0b00010000  → NOT a power of 2 ✓

Bit Manipulation in Embedded C

Embedded systems use bit manipulation constantly to control hardware registers. A typical pattern for a GPIO register:

/* Conceptual example — actual register addresses are MCU-specific */
#define GPIO_BASE    0x40020000UL
#define GPIO_MODER   (*(volatile unsigned int *)(GPIO_BASE + 0x00))
#define GPIO_ODR     (*(volatile unsigned int *)(GPIO_BASE + 0x14))
#define LED_PIN      5

/* Set pin 5 as output (mode = 0b01 in bits [11:10]) */
GPIO_MODER &= ~(0x3u << (LED_PIN * 2));   /* clear 2 bits */
GPIO_MODER |=  (0x1u << (LED_PIN * 2));   /* set output mode */

/* Toggle LED on pin 5 */
GPIO_ODR ^= (1u << LED_PIN);

The same four patterns — set, clear, toggle, check — appear in every embedded codebase. The only difference from the pure-C examples above is the volatile qualifier on the register pointer, which prevents the compiler from caching or eliminating the read/write.

Common Mistakes

  • Using int instead of unsigned int: left-shifting a 1 into the sign bit of a signed integer is undefined behaviour in C. Write 1u << n, not 1 << n.
  • Shifting by a negative amount or by ≥ bit width: both are undefined behaviour. Always validate n is in range before shifting.
  • XOR swap on aliased pointers: xor_swap(&a, &a) sets a to zero. The a != b guard handles this.
  • Forgetting operator precedence: &, |, and ^ have lower precedence than ==. Always parenthesise: (x & mask) == mask, not x & mask == mask.

Time and Space Complexity

Operation Time Notes
Set / Clear / Toggle / Check O(1) Single instruction on hardware
Count set bits (Kernighan) O(k) k = number of set bits — optimal for sparse values
Count set bits (naive loop) O(w) w = bit width (32 or 64)
Is power of two O(1) Two operations
Reverse bits O(w) w iterations; lookup table gives O(1) per byte

What This Teaches

  • The mask pattern: 1u << n creates a “probe” with exactly one bit set at position n. AND with a mask isolates bits; OR with a mask sets them; XOR flips them; AND with the complement clears them.
  • The subtract trick: x - 1 flips the lowest set bit from 1 to 0 and flips all bits below it from 0 to 1. Combining with x & (x-1) strips the lowest set bit — the foundation of Kernighan’s algorithm and the power-of-two check.
  • Two’s complement and bitwise NOT: -x == ~x + 1 in two’s complement, so x & (-x) isolates the lowest set bit.
  • Why embedded programmers love bit manipulation: a single 32-bit register can hold 32 flags, 8 nibbles, or 4 bytes — packed to fit in a cache line and read/written in one instruction.

Related Programs


As an Amazon Associate we earn from qualifying purchases.

Recommended Book

Chapter 2 of The C Programming Language by Kernighan & Ritchie covers bitwise operators, and exercises 2-6 through 2-9 are classic bit manipulation problems — all solved on this site. Also on Amazon.com.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>