Bitwise Operators Cheatsheet (C)
The operators, one bit at a time
| Op | Name | Rule (per bit pair a,b) |
|---|---|---|
& | AND | 1 only if both are 1 |
| | OR | 1 if either is 1 |
^ | XOR | 1 if they differ |
~ | NOT | flips the single bit |
<< | left shift | slides bits toward higher value (×2 per shift) |
>> | right shift | slides bits toward lower value (÷2 per shift) |
What each operator does when combined with a mask
This is the part worth memorizing — what a 1-bit vs a 0-bit in your mask
means, per operator:
| Op | mask bit = 1 means… | mask bit = 0 means… |
|---|---|---|
x & mask | keep that bit of x unchanged | force to 0 |
x | mask | force to 1 | keep that bit of x unchanged |
x ^ mask | flip that bit of x | keep that bit of x unchanged |
x & ~mask | force to 0 | keep that bit of x unchanged |
Everything below is a consequence of just this table.
Common idioms
x & mask // TEST/EXTRACT — isolate specific bits, zero the rest
x | mask // SET — force specific bits to 1, leave rest alone
x & ~mask // CLEAR — force specific bits to 0, leave rest alone
x ^ mask // TOGGLE — flip specific bits, leave rest alone
~x // flip EVERY bit
~x ^ mask // flip everything, then flip mask's 1-bits back
// → net effect: bits under mask=1 stay original,
// bits under mask=0 end up flipped
x ^ ~mask // identical result to ~x ^ mask (see Identities below)
(x >> i) & 1 // test whether bit i specifically is set (0 or 1)
x & (x - 1) // clear the lowest set bit
x & -x // isolate the lowest set bit (two's complement trick)
x | (1 << i) // set bit i
x & ~(1 << i) // clear bit i
x ^ (1 << i) // toggle bit i
Masks by width — remember the implicit zero-padding
A literal like 0xFF is widened to match the operand’s width by padding
with zeros on the left before the operation runs:
0xFF on a 32-bit int → 0x000000FF
That’s why x & 0xFF keeps only the last byte: the mask is 1-bits for
the byte you want, and implicit 0-bits for everything above it.
| Want to isolate | Mask |
|---|---|
| last byte | 0xFF |
| last 2 bytes | 0xFFFF |
| last nibble (4 bits) | 0xF |
bit i only | 1 << i |
| everything except the last byte | ~0xFF |
Shifts
x << k // multiply by 2^k. Fills k zeros on the right. Drops top k bits.
x >> k // divide by 2^k (roughly).
- Right shift on
unsigned: always logical — fills with0s on the left. Well-defined, no ambiguity. - Right shift on signed, negative
x: implementation-defined — in practice almost universally arithmetic (sign-extends, fills with1s) on real hardware/compilers, but not guaranteed by the standard. - Undefined behavior: shifting by a negative amount, or by an amount
>=the width of the type (x << 32on a 32-bitint). Always guard:if (k >= 0 && k < (int)(sizeof(x) * CHAR_BIT)) y = x << k;
Useful identities
~x ^ x == all 1s (a bit and its complement always differ)
~a ^ b == a ^ ~b (complementing either single operand of XOR
gives the same result)
a ^ b ^ b == a (XOR with the same value twice cancels —
this is the basis of the XOR swap)
a & a == a
a | a == a
a & ~a == 0
a | ~a == all 1s
~(~x) == x
(x & ~y) | (~x & y) == x ^ y
Set-operation view (bit vectors as sets)
If a bit vector encodes a set (bit i set ⟺ element i is in the set):
a & b // intersection
a | b // union
a ^ b // symmetric difference (elements in exactly one of the two)
~a // complement (relative to the full universe of bits)
a & ~b // set difference (A minus B)
Gotchas
- Operator precedence:
&,|,^bind looser than==/!=and comparisons.if (x & mask == 1)parses asx & (mask == 1)— almost always a bug. Always parenthesize:if ((x & mask) == 1). &&/||vs&/|: the double-character versions are logical operators (short-circuiting, operate on “truthy/falsy” as a whole, return0/1) — completely different from the single-character bitwise versions. Easy to typo one for the other.- Signed shift of negative numbers: right-shifting a negative signed
intis implementation-defined, not guaranteed zero-fill. Cast tounsignedfirst if you need guaranteed logical-shift behavior. - Mask width mismatches:
x & 0xFFon a 64-bitxstill only keeps the last byte — the mask pads with zeros to match, which is usually what you want, but worth double-checking when mixing types of different widths in one expression.
Quick self-test
Given x = 0x87654321 (32-bit):
| Expression | Result | Why |
|---|---|---|
x & 0xFF | 0x00000021 | keep last byte, zero rest |
x | 0xFF | 0x876543FF | force last byte to all 1s |
x ^ 0xFF | 0x876543DE | flip only the last byte |
~x | 0x789ABCDE | flip everything |
~x ^ 0xFF | 0x789ABC21 | flip everything, then un-flip last byte |
x << 4 | 0x76543210 | shift left 4, drop top nibble, zero-fill right |
x >> 4 (unsigned) | 0x08765432 | shift right 4, zero-fill left |