C Basics · beginner · ~15 min

Bitwise operations

Read, manipulate, and reason about individual bits using &, |, ^, ~, <<, >>.

Overview

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.

Why it matters

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.

Core concepts

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.

Syntax notes

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 */

Lesson

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.

Code examples

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... */

Line by line

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 */
}

Common mistakes

  • Shifting a signed int by 31 — undefined. Use 1u << 31 or 1UL << 63.
  • Forgetting to mask after shifting: (x >> 24) & 0xff not just x >> 24.

Debugging tips

Print bytes in hex with %08x. Use __builtin_popcount(x) (gcc/clang) to count set bits while debugging.

Memory safety

Shifts wider than the type width are undefined. UBSan with -fsanitize=undefined catches them at runtime.

Real-world uses

Permission bits in chmod, TCP flag bytes, PNG chunk flags, allocator chunk metadata, base64/hex encoders, every CRC/hash kernel.

Practice tasks

  1. Implement set/clear/toggle/extract from memory. 2. Write popcount(x) without __builtin_popcount. 3. Decode an 8-bit field into 8 booleans (e.g. the TCP flag byte).

Summary

Six operators; one-hot masks; always unsigned. With those three rules you have full control over the bit-level view of an integer.

Practice with these exercises