C Basics · beginner · ~15 min
Read, manipulate, and reason about individual bits using &, |, ^, ~, <<, >>.
C exposes six bitwise operators (&, |, ^, ~, <<, >>) that act on the binary representation of integers. Combined with shifts and masks, they let you set, clear, toggle, and extract individual bits — the substrate of bitmaps, packet headers, allocator metadata, and crypto primitives.
Higher-level languages hide bit operations behind libraries. C makes them first-class, which is why every network/file-format/embedded codebase is full of them. If you can't read x & ~mask, you can't read systems C.
Mask construction. 1u << n is a one-hot value with only bit n set; ~(1u << n) is the inverse — every bit set except n. Combine with AND/OR to set, clear, or test individual bits without touching their neighbours.
Shift discipline. Shifting a signed integer left into the sign bit is undefined; shifting by >= the type width is undefined. Always use unsigned literals (1u, 1UL) and explicit type widths.
Pentester mindset. Heap allocator metadata, PE/ELF section flags, page-table entries, and TCP flag bytes are all packed into bits. Read them with shifts + masks; never memcpy raw bytes into a struct unless you control alignment.
Defensive coding habit. Always mask after shifting (& 0xff) so stray high bits don't leak. Always use unsigned types for bitwise work.
x | mask /* set the bits in mask */
x & ~mask /* clear the bits in mask */
x ^ mask /* flip the bits in mask */
(x >> n) & 0xff /* extract byte n */
1u << n /* one-hot mask */
Every C integer is a bag of bits. The bitwise operators expose that view directly. AND, OR, XOR, NOT, shift-left, shift-right — six operators do everything from packet-header parsing to access-bit checks in OS kernels.
unsigned x = 0b1100;
unsigned y = 0b1010;
printf("%u %u %u %u\n", x & y, x | y, x ^ y, ~x);
/* 8 14 6 ...high bits set... */
unsigned set_bit(unsigned x, int n){
return x | (1u << n); /* OR with a one-hot mask */
}
unsigned clear_bit(unsigned x, int n){
return x & ~(1u << n); /* AND with the inverse of a one-hot mask */
}
unsigned toggle_bit(unsigned x, int n){
return x ^ (1u << n); /* XOR flips just that bit */
}
1u << 31 or 1UL << 63.(x >> 24) & 0xff not just x >> 24.Print bytes in hex with %08x. Use __builtin_popcount(x) (gcc/clang) to count set bits while debugging.
Shifts wider than the type width are undefined. UBSan with -fsanitize=undefined catches them at runtime.
Permission bits in chmod, TCP flag bytes, PNG chunk flags, allocator chunk metadata, base64/hex encoders, every CRC/hash kernel.
popcount(x) without __builtin_popcount. 3. Decode an 8-bit field into 8 booleans (e.g. the TCP flag byte).Six operators; one-hot masks; always unsigned. With those three rules you have full control over the bit-level view of an integer.