Why this matters
So far you have treated integers as numbers. Bitwise operators let you treat them as what they physically are: rows of bits. That is how hardware registers are programmed, how file permissions (rwxr-xr-x) are stored, how network protocols pack fields into headers, how compression and cryptography work, and how many fast algorithms squeeze sets of flags into a single word. The operators are simple; using them confidently takes practice with the binary picture in your head.
The picture
Recall from the types module that an unsigned char is 8 bits and bit 0 is the least significant (rightmost):
bit: 7 6 5 4 3 2 1 0
value: 128 64 32 16 8 4 2 1
0xCA = 1 1 0 0 1 0 1 0 = 128 + 64 + 8 + 2 = 202
Hexadecimal is the natural notation for bits because each hex digit is exactly four bits: 0xCA is 1100 1010. Get used to reading hex this way; it is why programmers prefer it.
The six operators
Each bitwise operator works on every bit position independently (except the shifts):
operator name result bit is 1 when a = 1100 1010, b = 0101 1100
a & b AND both bits are 1 0100 1000
a | b OR either bit is 1 1101 1110
a ^ b XOR the bits differ 1001 0110
~a complement the bit was 0 0011 0101
a << n left shift bits move n places left, zeros enter on the right
a << 2 = 0010 1000 (8-bit view)
a >> n right shift bits move n places right a >> 2 = 0011 0010
The compound forms &=, |=, ^=, <<= and >>= work like +=.
Here is a program that prints these with a small helper:
#include <stdio.h>
void print_bits(unsigned char x)
{
for (int i = 7; i >= 0; i--)
putchar((x >> i) & 1u ? '1' : '0');
putchar('\n');
}
int main(void)
{
unsigned char a = 0xCA; /* 1100 1010 */
unsigned char b = 0x5C; /* 0101 1100 */
print_bits(a & b);
print_bits(a | b);
print_bits(a ^ b);
print_bits((unsigned char)~a);
print_bits((unsigned char)(a << 2));
print_bits(a >> 2);
return 0;
}
01001000
11011110
10010110
00110101
00101000
00110010
print_bits is worth understanding fully: x >> i moves bit i down to position 0, and & 1u throws away every other bit, leaving 0 or 1. This "shift then mask" idiom extracts any bit.
Note: operands narrower than
intare promoted tointbefore any arithmetic, including bitwise operations.~aon anunsigned char0xCA is computed as theint~202, which is-203(0xFFFFFF35). The cast back tounsigned charkeeps only the low 8 bits. This is why bit manipulation is usually done onunsigned intor the fixed-width types likeuint32_t.
Logical versus bitwise
&&, || and ! treat a whole value as true (nonzero) or false (zero) and produce 0 or 1. &, | and ~ work bit by bit and produce any value:
12 && 10 /* 1: both are nonzero */
12 & 10 /* 8: 1100 & 1010 = 1000 */
!12 /* 0 */
~12u /* 4294967283: every bit flipped */
&& and || also short-circuit (stop evaluating as soon as the answer is known); & and | always evaluate both sides. Mixing them up is a classic bug that compiles silently.
Shifts are multiplication and division by powers of two
Shifting left by n multiplies by 2^n; shifting an unsigned value right by n divides by 2^n, discarding the remainder:
1u << 4 /* 16 */
5u << 3 /* 40 */
0x80u >> 3 /* 16 */
200u >> 1 /* 100 */
Bits shifted out of either end are lost. Shifts are cheap, but do not write x << 3 to mean x * 8 for speed: compilers already do that, and x * 8 says what you mean.
Signed shifts: rules to remember
- Right-shifting a negative number is implementation-defined. gcc does an arithmetic shift, copying the sign bit in from the left, so
-16 >> 2is-4. Note that it rounds toward negative infinity:-17 >> 2is-5, while-17 / 4is-4. - Left-shifting a negative number, or shifting a 1 into or past the sign bit, is undefined behavior:
1 << 31with a 32-bitintis UB. Write1u << 31. - Shifting by a negative amount, or by at least the width of the type (
x << 32for a 32-bitx) is undefined, and on x86 really does give strange results because the CPU only uses the low 5 bits of the count.
The simple rule: do bit manipulation on unsigned types, and write constants with a u suffix.
Precedence
The bitwise operators have surprisingly low precedence, below the comparisons:
highest ~ ! (unary)
* / %
+ -
<< >>
< <= > >=
== !=
&
^
|
&&
lowest || ?: =
So x & 1 == 0 means x & (1 == 0), and 1 << n + 1 means 1 << (n + 1). gcc warns about the first (-Wparentheses). The cure is always the same: parenthesize every bitwise subexpression, (x & 1) == 0.
Common mistakes
- Using
&where&&was meant, or vice versa.if (flags & READ && flags & WRITE)happens to work;if (a & b)witha = 1, b = 2is false even though both are "true". - Forgetting the precedence rules. Read the warning:
warning: suggest parentheses around comparison in operand of '&' [-Wparentheses] - Shifting signed values, especially
1 << 31. Use1u << 31. - Shifting by the full width to "clear" a value:
x >> 32is UB for a 32-bitx, not 0. - Expecting
~on a small type to stay small:~(unsigned char)0is theint-1, not 255.
Recap
&(both),|(either),^(different) and~(flip) operate on each bit;<<and>>move bits.(x >> i) & 1extracts biti.- Left shift multiplies by 2^n, right shift of unsigned divides by 2^n.
- Shifting signed values into the sign bit, or by the full width or more, is undefined; use unsigned types and
uconstants. - Bitwise operators bind more loosely than
==: parenthesize.
Going further: in <stdint.h>, uint8_t, uint16_t, uint32_t and uint64_t give exact widths for bit work. Print a uint32_t in hex with printf("%08x\n", value) and try predicting the results of a few shifts before running them.