C bitwise operators
last modified November 14, 2026
The bitwise operators work on the binary representation of integers instead of on their numeric value. They are the tool for flag sets, masks, packed fields, checksums and low-level protocols, and they are the only way to express some of those ideas in C. This tutorial covers the six operators, how to set, clear and test individual bits, the effect and the limits of shifting, bit counting, and packing several values into one integer.
Bits and binary notation
In an integer, bit 0 is the least significant bit and its value is 1, bit 1 is worth 2, bit 2 is worth 4, and so on. Values written in hexadecimal are convenient here because each hex digit stands for exactly four bits. The next program prints the eight bits of a byte, most significant first.
#include <stdio.h>
static void print_bits(unsigned char v) {
for (int i = 7; i >= 0; i--) {
printf("%u", (v >> i) & 1u);
}
printf("\n");
}
int main(void) {
print_bits(0x00);
print_bits(0x01);
print_bits(0xA5);
print_bits(0xFF);
return 0;
}
The loop shifts the value right by i places and masks the result
with 1, so exactly one bit remains. 0xA5 is 10100101 in binary:
the high nibble A is 1010 and the low nibble 5 is 0101. Note the u
suffix on the constant 1 and the parameter type unsigned char:
right-shifting a negative signed value is implementation-defined, so the
program avoids signed operands altogether.
$ gcc -std=c17 -Wall -Wextra -o print_bits print_bits.c $ ./print_bits 00000000 00000001 10100101 11111111
The six operators
& is bitwise AND, | is OR, ^ is
exclusive OR and ~ is complement; << and
>> shift the bits left and right. AND keeps a bit only when
both operands have it, OR keeps it when either does, and XOR keeps it when the
operands differ.
#include <stdio.h>
int main(void) {
unsigned int a = 0x0F;
unsigned int b = 0x35;
printf("a & b = 0x%02X\n", a & b);
printf("a | b = 0x%02X\n", a | b);
printf("a ^ b = 0x%02X\n", a ^ b);
printf("~a = 0x%08X\n", ~a);
printf("a << 2 = 0x%02X\n", a << 2);
printf("b >> 2 = 0x%02X\n", b >> 2);
return 0;
}
The results can be checked by hand: the bits common to 00001111 and 00110101
are 00000101, which is 0x05, and their union is 00111111, which is 0x3F.
~a flips every bit of a 32-bit unsigned int, so the
leading zeros become ones and the value prints as 0xFFFFFFF0 with eight hex
digits. The shifts move the pattern by two positions and fill the vacated bits
with zero.
$ gcc -std=c17 -Wall -Wextra -o basic_ops basic_ops.c $ ./basic_ops a & b = 0x05 a | b = 0x3F a ^ b = 0x3A ~a = 0xFFFFFFF0 a << 2 = 0x3C b >> 2 = 0x0D
Setting, clearing and testing a bit
A mask is a value with ones exactly where a bit is to be affected. OR-ing with
1u << n sets bit n, AND-ing with its complement
clears the bit, XOR-ing toggles it, and AND-ing with the mask alone tests it.
#include <stdio.h>
int main(void) {
unsigned int value = 0x00;
value |= 1u << 3;
printf("after set bit 3: 0x%02X\n", value);
value |= 1u << 5;
printf("after set bit 5: 0x%02X\n", value);
value &= ~(1u << 3);
printf("after clear bit 3: 0x%02X\n", value);
value ^= 1u << 5;
printf("after toggle bit 5: 0x%02X\n", value);
printf("bit 3 is %s\n", (value & (1u << 3)) ? "set" : "clear");
return 0;
}
Setting bit 3 turns 0x00 into 0x08 and setting bit 5 adds 0x20, giving 0x28.
Clearing bit 3 subtracts 0x08 and leaves 0x20, and toggling bit 5 removes it
again. The test at the end does not compare the masked value with 1: any
non-zero result is true, and writing != 0 or leaving the expression
as it is are both conventional. The parentheses around the mask matter, because
& has lower precedence than ==.
$ gcc -std=c17 -Wall -Wextra -o masks masks.c $ ./masks after set bit 3: 0x08 after set bit 5: 0x28 after clear bit 3: 0x20 after toggle bit 5: 0x00 bit 3 is clear
Flag sets
A common use of the operators is a set of independent options, where each option is a single bit and a variable holds the union of the selected ones. Enumeration constants name the bits, which keeps the code readable and the values in one place.
#include <stdio.h>
enum {
READ = 1u << 0,
WRITE = 1u << 1,
EXECUTE = 1u << 2
};
int main(void) {
unsigned int perms = READ | WRITE;
printf("perms = 0x%X\n", perms);
printf("read: %d\n", (perms & READ) != 0);
printf("execute: %d\n", (perms & EXECUTE) != 0);
perms |= EXECUTE;
perms &= ~WRITE;
printf("after changes: 0x%X\n", perms);
printf("write: %d\n", (perms & WRITE) != 0);
return 0;
}
READ | WRITE combines two flags into 0x3, and each test isolates
one bit. The two updates are the same set and clear operations as before, only
written with the named constants. The bitwise operators are used instead of
+ and - because OR and AND are idempotent: setting an
already set flag changes nothing, whereas adding it twice would corrupt the
value.
$ gcc -std=c17 -Wall -Wextra -o flags flags.c $ ./flags perms = 0x3 read: 1 execute: 0 after changes: 0x5 write: 0
Shifting
Shifting left by n multiplies an unsigned value by 2n, and shifting right divides it, discarding the remainder. Shifting is often faster than multiplication and is the standard way to build masks and packed values.
#include <stdio.h>
int main(void) {
for (int i = 0; i <= 8; i++) {
printf("1 << %d = %u\n", i, 1u << i);
}
printf("1u << 31 = %u\n", 1u << 31);
printf("-8 >> 1 = %d\n", -8 >> 1);
return 0;
}
Each step doubles the value until 28 is 256. 1u << 31
sets the top bit of a 32-bit unsigned integer and prints 2147483648, a value
that int cannot represent. The last line shifts a negative signed
value: on this platform the sign bit is copied into the vacated position, so the
result is -4, but the standard leaves that choice to the implementation and a
portable program does not depend on it.
$ gcc -std=c17 -Wall -Wextra -o shifts shifts.c $ ./shifts 1 << 0 = 1 1 << 1 = 2 1 << 2 = 4 1 << 3 = 8 1 << 4 = 16 1 << 5 = 32 1 << 6 = 64 1 << 7 = 128 1 << 8 = 256 1u << 31 = 2147483648 -8 >> 1 = -4
Shifting out of range
A shift count that is negative or greater than or equal to the width of the type
is undefined behavior, and so is a left shift of a signed value whose result does
not fit. The next program shifts 1 into the sign bit of an int.
#include <stdio.h>
int main(void) {
int v = 1 << 31;
printf("%d\n", v);
return 0;
}
The constant 1 has type int, so the shift is performed on a signed
32-bit value and 231 is one more than INT_MAX. The
sanitizer flags the operation, and the program still prints -2147483648 because
the machine simply shifts the bits. Writing 1u << 31 makes
the operation defined and yields the expected unsigned value; this is why masks
and shifts are usually written on unsigned types.
$ gcc -std=c17 -Wall -Wextra -fsanitize=undefined -o shift_overflow shift_overflow.c $ ./shift_overflow shift_overflow.c:5:15: runtime error: left shift of 1 by 31 places cannot be represented in type 'int' -2147483648
Counting set bits
A loop that clears the lowest set bit on every pass counts the ones in a value.
The expression v & (v - 1) removes exactly one set bit, so the
loop runs as many times as there are ones rather than as many times as there are
bits.
#include <stdio.h>
static int count_bits(unsigned int v) {
int count = 0;
while (v != 0) {
v &= v - 1;
count++;
}
return count;
}
int main(void) {
printf("0x00: %d\n", count_bits(0x00u));
printf("0x0F: %d\n", count_bits(0x0Fu));
printf("0xA5: %d\n", count_bits(0xA5u));
printf("0xFF: %d\n", count_bits(0xFFu));
printf("0xFFFFFFFF: %d\n", count_bits(0xFFFFFFFFu));
return 0;
}
Subtracting 1 from a value turns its lowest set bit into 0 and all the bits
below it into 1, so AND-ing with the original clears that bit and leaves the
rest unchanged. The argument is unsigned, which matters for the same reason as
before: the subtraction must wrap for the trick to work. GCC and Clang provide
__builtin_popcount, but a portable function is worth knowing.
$ gcc -std=c17 -Wall -Wextra -o popcount popcount.c $ ./popcount 0x00: 0 0x0F: 4 0xA5: 4 0xFF: 8 0xFFFFFFFF: 32
Packing fields into one integer
When several small values must be stored together, each can occupy a fixed range of bits. Shifting moves a field into its position, masking isolates it again, and OR combines the parts. A color in the usual 0xRRGGBB form is the classic example.
#include <stdio.h>
int main(void) {
unsigned int color = 0x3F7AC8;
unsigned int red = (color >> 16) & 0xFF;
unsigned int green = (color >> 8) & 0xFF;
unsigned int blue = color & 0xFF;
printf("color: #%06X\n", color);
printf("red: %u\n", red);
printf("green: %u\n", green);
printf("blue: %u\n", blue);
unsigned int repacked = (red << 16) | (green << 8) | blue;
printf("repacked: #%06X\n", repacked);
return 0;
}
Red occupies bits 16 to 23, green bits 8 to 15 and blue the low byte, so the
shifts move each field to the right edge and 0xFF masks away
everything else. Packing reverses the process: each component is shifted into
place and the three are OR-ed together. The value 0x3F7AC8 unpacks to 63, 122
and 200 and packs back to the same number, which shows that the two operations
are inverses of each other.
$ gcc -std=c17 -Wall -Wextra -o rgb rgb.c $ ./rgb color: #3F7AC8 red: 63 green: 122 blue: 200 repacked: #3F7AC8
Guidelines
The rules below keep bit manipulation predictable. Almost every surprise in this area comes from an operand that was signed when it should have been unsigned.
- Use unsigned types for values that are treated as bit patterns.
- Write masks as
1u << n, not1 << n. - Do not shift by a negative count or by the width of the type or more.
- Parenthesize masks and tests, since
&and|bind looser than comparisons. - Use named constants for flags instead of bare hex numbers.
- Use
^to toggle and|=to set; adding a flag that is already set is a bug. - Do not rely on the sign of a right shift of a negative value.
Source
cppreference: bitwise operators
cppreference: other operators
cppreference: integer types
GCC: built-in functions
This tutorial covered the bitwise operators of C: binary notation, AND, OR, XOR and complement, setting, clearing, toggling and testing bits with masks, flag sets, shifting and its undefined cases, counting set bits, and packing fields into one integer. The arithmetic operators are described in C operators, and the ranges of the integer types in C integer limits.
Author
List all C tutorials.