Bitwise operators work directly on the binary representation of integers. They are faster than arithmetic operators and essential for systems programming, embedded development, and any code that deals with hardware registers, file formats, or network protocols.

The Six Bitwise Operators

Operator Name Effect
& AND 1 only if both bits are 1
| OR 1 if either bit is 1
^ XOR 1 if bits differ
~ NOT Flips all bits
<< Left shift Shifts bits left, fills with 0
>> Right shift Shifts bits right
#include <stdio.h>

int main() {
    unsigned char a = 0b11001010;   /* 202 */
    unsigned char b = 0b10110101;   /* 181 */

    printf("a & b  = %08b (%d)n", a & b,  a & b);   /* AND  */
    printf("a | b  = %08b (%d)n", a | b,  a | b);   /* OR   */
    printf("a ^ b  = %08b (%d)n", a ^ b,  a ^ b);   /* XOR  */
    printf("~a     = %08b (%d)n", (unsigned char)~a, (unsigned char)~a); /* NOT */
    printf("a << 2 = %08b (%d)n", (unsigned char)(a << 2), (unsigned char)(a <> 2 = %08b (%d)n", a >> 2, a >> 2);

    return 0;
}

Practical Use Case 1 — Bit Flags

Use bit flags to pack multiple boolean options into a single integer, saving memory and enabling fast combined checks:

#include <stdio.h>

#define FLAG_READ    (1 << 0)   /* 0001 */
#define FLAG_WRITE   (1 << 1)  /* 0010 */
#define FLAG_EXECUTE (1 << 2)  /* 0100 */

int main() {
    unsigned int perms = 0;

    /* Set flags */
    perms |= FLAG_READ;
    perms |= FLAG_WRITE;

    /* Check a flag */
    if (perms & FLAG_READ) {
        printf("Read permission grantedn");
    }

    /* Clear a flag */
    perms &= ~FLAG_WRITE;

    /* Toggle a flag */
    perms ^= FLAG_EXECUTE;

    printf("Permissions: %dn", perms);
    return 0;
}
  • perms |= FLAG — set a flag (turn the bit on)
  • perms &= ~FLAG — clear a flag (turn the bit off)
  • perms & FLAG — test a flag (check if the bit is on)
  • perms ^= FLAG — toggle a flag (flip the bit)

Practical Use Case 2 — Fast Multiply and Divide by Powers of 2

int x = 7;
int doubled  = x << 1;   /* 14 — same as x * 2  */
int quadruple = x << 2;  /* 28 — same as x * 4  */
int halved   = x >> 1;   /* 3  — same as x / 2 (integer division) */

Left shift by n multiplies by 2ⁿ. Right shift by n divides by 2ⁿ (for unsigned integers). Modern compilers do this automatically when you write x * 2, so write what is readable and let the compiler optimise.

Practical Use Case 3 — Extract and Pack Bytes

#include <stdio.h>

int main() {
    /* Pack two 8-bit values into a 16-bit integer */
    unsigned char high = 0xAB;
    unsigned char low  = 0xCD;
    unsigned short packed = ((unsigned short)high << 8) | low;
    printf("Packed: 0x%04Xn", packed);   /* 0xABCD */

    /* Extract the bytes back */
    unsigned char extracted_high = (packed >> 8) & 0xFF;
    unsigned char extracted_low  = packed & 0xFF;
    printf("High: 0x%02X, Low: 0x%02Xn", extracted_high, extracted_low);

    return 0;
}

This pattern is used constantly in network protocol parsing, file format reading, and hardware register manipulation.

Practical Use Case 4 — Check if a Number Is Even or Odd

if (n & 1) {
    printf("oddn");
} else {
    printf("evenn");
}

The least significant bit of any odd number is 1. This is slightly faster than n % 2, though any decent compiler will generate the same code for both.

Right Shift and Signed Integers — Be Careful

int x = -8;
printf("%dn", x >> 1);   /* implementation-defined for negative values */

Right-shifting a negative signed integer is implementation-defined behaviour in C. On most platforms (including x86), it performs an arithmetic right shift (sign-extending), giving -4. But this is not guaranteed. Use unsigned integers when working with bits to avoid this ambiguity.

Enable -Wall when working with shifts to catch potential issues — see our guide on GCC warning flags for the full set of flags that catch bit manipulation bugs.

XOR Swap — The Classic Trick

int a = 5, b = 10;
a = a ^ b;   /* a = 0101 ^ 1010 = 1111 */
b = a ^ b;   /* b = 1111 ^ 1010 = 0101 (original a) */
a = a ^ b;   /* a = 1111 ^ 0101 = 1010 (original b) */
printf("a=%d b=%dn", a, b);   /* a=10 b=5 */

This swaps two integers without a temporary variable. It fails if a and b are the same variable (XOR with itself gives 0). It is a curiosity — just use a temp variable in real code.

TL;DR

  • & AND, | OR, ^ XOR, ~ NOT, << left shift, >> right shift
  • Use |= to set flags, &= ~ to clear, & to test, ^= to toggle
  • Use (1 << n) to create a bitmask for bit n
  • Always use unsigned integers for bitwise operations — signed right shift is implementation-defined
  • Bit shifting is the correct way to extract or pack values from multi-byte integers
  • Test all bit manipulation examples instantly in our c compiler — paste, run, verify