Bitwise Operators
Bitwise operators work directly on the individual binary digits (bits) that make up an integer's representation in memory. They are essential for low-level programming: device drivers, network protocols, embedded systems, and any code that packs multiple flags into a single value.
Operator | Name | Behavior |
|---|---|---|
| AND | Result bit is 1 only if both input bits are 1 |
| OR | Result bit is 1 if either input bit is 1 |
| XOR | Result bit is 1 if the input bits differ |
| NOT | Flips every bit (unary operator) |
| Left shift | Shifts bits left, filling with zeros |
| Right shift | Shifts bits right |
#include <stdio.h>
int main(void) {
unsigned int a = 12; // binary: 1100
unsigned int b = 10; // binary: 1010
printf("%u\n", a & b); // 8 (1000)
printf("%u\n", a | b); // 14 (1110)
printf("%u\n", a ^ b); // 6 (0110)
printf("%u\n", ~a); // implementation-defined bit pattern, all bits flipped
printf("%u\n", a << 1); // 24 (1100 becomes 11000)
printf("%u\n", a >> 1); // 6 (1100 becomes 0110)
return 0;
}Shifting as Fast Multiply/Divide by Powers of Two
n is equivalent to multiplying by 2^n, and shifting right by n is equivalent to dividing by 2^n (for non-negative values). Compilers already perform this optimization automatically when it is safe to do so, but understanding the relationship helps when reading low-level code.int value = 5;
printf("%d\n", value << 2); // 20, same as 5 * 4
printf("%d\n", value >> 1); // 2, same as 5 / 2 (integer division)Practical Use: Bit Flags for Settings
A very common pattern is packing several independent on/off options into a single byte or integer, where each bit represents one option. This saves memory and lets you test, set, or clear flags with a single bitwise operation instead of several boolean variables.
#include <stdio.h>
#define PERM_READ (1 << 0) // 0001
#define PERM_WRITE (1 << 1) // 0010
#define PERM_EXECUTE (1 << 2) // 0100
#define PERM_DELETE (1 << 3) // 1000
int main(void) {
unsigned char permissions = 0;
permissions |= PERM_READ; // set the READ flag
permissions |= PERM_WRITE; // set the WRITE flag
if (permissions & PERM_WRITE) {
printf("write access granted\n");
}
permissions &= ~PERM_WRITE; // clear the WRITE flag, keep the rest
if (!(permissions & PERM_WRITE)) {
printf("write access revoked\n");
}
permissions ^= PERM_EXECUTE; // toggle EXECUTE
printf("final permissions: %d\n", permissions);
return 0;
}flags &= ~SOME_FLAG; is the standard way to clear a single bit: ~SOME_FLAG produces a mask with every bit set except the one you want to clear, and AND-ing with it leaves all other bits untouched.Right-Shifting Negative Numbers
negative signed integer is implementation- defined behavior in C: most compilers perform an arithmetic shift (copying the sign bit into the vacated high bits, so the value stays negative), but the standard does not guarantee this. If you need predictable bit manipulation, perform shifts on unsigned types, where the behavior is always a well-defined logical shift that fills with zeros.int negative = -8;
printf("%d\n", negative >> 1); // typically -4, but not guaranteed by the standard
unsigned int positive = 8u;
printf("%u\n", positive >> 1); // always 4 — well defined for unsigned types&,|,^,~,<<,>>operate on the binary representation of integersLeft shift multiplies by a power of two; right shift divides (for non-negative values)
Bit flags let you pack multiple boolean options into one integer using OR to set, AND-NOT to clear, and AND to test
Prefer
unsignedtypes for shifting to avoid implementation-defined behavior on negative numbers