C Programming 4 min read

Operators and bit operations

&& and || are logical and give 0 or 1; &, |, ^ and ~ work bit by bit. Set a bit with |= 1u << n, clear it with &= ~(1u << n) and toggle it with ^=. Use unsigned constants: shifting into a signed sign bit is undefined.

A byte starts as 1010 0101, 0xA5. Setting bit 1 with |= 1u << 1 gives 1010 0111, 0xA7. Clearing bit 5 with &= ~(1u << 5) gives 1000 0111, 0x87. Toggling bit 0 with ^= 1u << 0 gives 1000 0110, 0x86. Each step changes one bit.
One byte, three operations: each changes one bit and leaves the other seven alone.

Logical or bitwise?

The logical operators &&, || and ! treat a whole value as true (non-zero) or false (zero), give an int 0 or 1, and stop early: in a && b, b is not evaluated when a is false. The bitwise operators &, |, ^ and ~ work on every bit separately and always evaluate both sides. So 2 && 1 is 1, but 2 & 1 is 0, because the two values share no set bit.

Shifts and masks

1u << n is a mask with only bit n set. With it, four idioms cover most register code: reg |= 1u << n sets the bit, reg &= ~(1u << n) clears it, reg ^= 1u << n toggles it, and (reg >> n) & 1u reads it. Write 1u, not 1: with a 32-bit int, 1 << 31 shifts into the sign bit, which is undefined, and right-shifting a negative value is implementation-defined.

bits.cC
#include <stdint.h>#include <stdio.h>static void show(const char *label, uint8_t v) {    printf("%-9s0x%02X  ", label, (unsigned)v);    for (int i = 7; i >= 0; i--) {        putchar(((v >> i) & 1u) ? '1' : '0');    }    putchar('\n');}int main(void) {    uint8_t reg = 0xA5;               /* 1010 0101 */    show("start", reg);    reg |= (uint8_t)(1u << 1);        /* set bit 1    */    show("set 1", reg);    reg &= (uint8_t)~(1u << 5);       /* clear bit 5  */    show("clear 5", reg);    reg ^= (uint8_t)(1u << 0);        /* toggle bit 0 */    show("toggle 0", reg);    printf("bit 7 is %u\n", (unsigned)((reg >> 7) & 1u));    printf("2 && 1 = %d, 2 & 1 = %d\n", 2 && 1, 2 & 1);    return 0;}
Output
start    0xA5  10100101
set 1    0xA7  10100111
clear 5  0x87  10000111
toggle 0 0x86  10000110
bit 7 is 1
2 && 1 = 1, 2 & 1 = 0

Fields and read-modify-write

A field of several bits is updated in two steps: clear it, then insert the new value, reg = (reg & ~MASK) | ((val << SHIFT) & MASK). On a hardware register, even |= is three steps: read, modify, write. If an interrupt changes the same register in between, its update is lost. Many MCUs therefore provide separate set and clear registers that change chosen bits in a single write; check your chip's reference manual.