Skip to content
Laviyx Learn

Try

C course menu

Bitwise Operators in C: AND, OR, XOR, NOT, Shifts and Bit Masks

Learn bitwise operations in C: the &, |, ^, ~, << and >> operators, setting, clearing, toggling and testing bits with masks, flags packed into one integer, fixed-width types from stdint.h, counting set bits, and classic bit tricks used in embedded systems and interviews.

Advanced10 min readUpdated
On this page

Bitwise operators work on the individual bits of integers. They are essential in embedded systems (where a single register bit can switch on an LED or a motor), in networking and file formats, for compact sets of flags, and in many interview questions.

The operators#

OperatorNameResult bit is 1 when
a & bANDBoth bits are 1
a | bORAt least one bit is 1
a ^ bXORThe bits differ
~aNOTThe bit was 0 (flips every bit)
a << nLeft shiftBits move n places left; zeros fill in
a >> nRight shiftBits move n places right
C
#include <stdio.h>

void print_bits(unsigned char x) {
    for (int i = 7; i >= 0; i--) putchar((x >> i) & 1 ? '1' : '0');
}

int main(void) {
    unsigned char a = 0xCA, b = 0x6C;     /* 11001010 and 01101100 */
    printf("a      "); print_bits(a); printf("\n");
    printf("b      "); print_bits(b); printf("\n");
    printf("a & b  "); print_bits(a & b); printf("\n");
    printf("a | b  "); print_bits(a | b); printf("\n");
    printf("a ^ b  "); print_bits(a ^ b); printf("\n");
    printf("~a     "); print_bits((unsigned char)~a); printf("\n");
    printf("a << 1 "); print_bits((unsigned char)(a << 1)); printf("\n");
    printf("a >> 2 "); print_bits(a >> 2); printf("\n");
    return 0;
}
Output
a      11001010
b      01101100
a & b  01001000
a | b  11101110
a ^ b  10100110
~a     00110101
a << 1 10010100
a >> 2 00110010
  • x << n multiplies by 2ⁿ and x >> n divides by 2ⁿ for non-negative values, as long as nothing overflows.
  • Use unsigned types for bit manipulation. Shifting a negative number right, or shifting a 1 into a signed number's sign bit, has implementation-defined or undefined results.
  • Do not confuse & and | with the logical && and ||.

Fixed-width types#

The size of int varies between platforms, so bit-level code usually uses the exact-width types from <stdint.h>: uint8_t, uint16_t, uint32_t, uint64_t (and signed int32_t and so on).

Masks: set, clear, toggle and test#

A mask selects bits. 1u << n is a mask with only bit n set:

C
#include <stdio.h>
#include <stdint.h>

int main(void) {
    uint8_t reg = 0;                 /* imagine an 8-bit hardware register */

    reg |= 1u << 3;                  /* set bit 3 */
    reg |= 1u << 0;                  /* set bit 0 */
    printf("after set:    0x%02X\n", reg);

    reg &= (uint8_t)~(1u << 0);      /* clear bit 0 */
    printf("after clear:  0x%02X\n", reg);

    reg ^= 1u << 7;                  /* toggle bit 7 */
    printf("after toggle: 0x%02X\n", reg);

    printf("bit 3 is %s\n", (reg & (1u << 3)) ? "on" : "off");
    printf("bit 4 is %s\n", (reg & (1u << 4)) ? "on" : "off");
    return 0;
}
Output
after set:    0x09
after clear:  0x08
after toggle: 0x88
bit 3 is on
bit 4 is off
TaskCode
Set bit nx |= 1u << n
Clear bit nx &= ~(1u << n)
Toggle bit nx ^= 1u << n
Test bit n(x >> n) & 1u or x & (1u << n)

Flags#

Several yes/no options fit in one integer, each one a power of two. This is how many APIs pass options, such as file permissions on Unix:

C
#include <stdio.h>

enum {
    PERM_READ  = 1u << 0,
    PERM_WRITE = 1u << 1,
    PERM_EXEC  = 1u << 2,
};

void describe(unsigned perms) {
    printf("%c%c%c\n",
           perms & PERM_READ  ? 'r' : '-',
           perms & PERM_WRITE ? 'w' : '-',
           perms & PERM_EXEC  ? 'x' : '-');
}

int main(void) {
    unsigned user = PERM_READ | PERM_WRITE;
    describe(user);
    user |= PERM_EXEC;
    user &= ~PERM_WRITE;
    describe(user);
    return 0;
}
Output
rw-
r-x

Extracting fields#

Shift, then mask, to read a group of bits. For example, the red, green and blue parts of a 24-bit colour:

C
#include <stdio.h>
#include <stdint.h>

int main(void) {
    uint32_t colour = 0x3F6283;          /* the C course's accent colour */
    unsigned r = (colour >> 16) & 0xFF;
    unsigned g = (colour >> 8) & 0xFF;
    unsigned b = colour & 0xFF;
    printf("r=%u g=%u b=%u\n", r, g, b);

    uint32_t packed = (r << 16) | (g << 8) | b;
    printf("0x%06X\n", (unsigned)packed);
    return 0;
}
Output
r=63 g=98 b=131
0x3F6283

Classic bit tricks#

C
#include <stdio.h>

int count_bits(unsigned x) {
    int count = 0;
    while (x) {
        x &= x - 1;          /* clears the lowest set bit */
        count++;
    }
    return count;
}

int is_power_of_two(unsigned x) {
    return x != 0 && (x & (x - 1)) == 0;
}

int main(void) {
    printf("%d %d\n", count_bits(0xF0u), count_bits(255u));
    printf("%d %d %d\n", is_power_of_two(64), is_power_of_two(96), is_power_of_two(0));
    printf("odd? %u\n", 37u & 1u);

    int a = 5, b = 9;
    a ^= b; b ^= a; a ^= b;     /* swap without a temporary: a curiosity, not faster */
    printf("%d %d\n", a, b);
    return 0;
}
Output
4 8
1 0 0
odd? 1
9 5

x & (x - 1) removes the lowest 1 bit, so a power of two (exactly one bit set) becomes 0. The XOR swap is a well-known interview puzzle, but a normal swap with a temporary is clearer and at least as fast.

Practice#

Practice exercise

Write unsigned reverse_bits8(unsigned x) that reverses the lowest 8 bits of x (bit 0 becomes bit 7, and so on), and test it on 0x01, 0xF0 and 0xB4. Print the results in hexadecimal.

Show solution
C
#include <stdio.h>

unsigned reverse_bits8(unsigned x) {
    unsigned result = 0;
    for (int i = 0; i < 8; i++) {
        result = (result << 1) | ((x >> i) & 1u);
    }
    return result;
}

int main(void) {
    printf("0x%02X 0x%02X 0x%02X\n", reverse_bits8(0x01), reverse_bits8(0xF0), reverse_bits8(0xB4));
    return 0;
}
Output
0x80 0x0F 0x2D

Key takeaway#

Finished this lesson?