Advanced 14 min

Bit Manipulation

Master bitwise operations for efficient low-level programming

Learn bit manipulation - the foundation of low-level programming, optimization, and systems programming.

A Simple Example

#include <iostream>
#include <bitset>

enum class Permission {
    Read    = 1 << 0,  // 0001
    Write   = 1 << 1,  // 0010
    Execute = 1 << 2,  // 0100
    Delete  = 1 << 3   // 1000
};

class FilePermissions {
    unsigned int permissions;

public:
    FilePermissions() : permissions{0} {}

    void grant(Permission perm) {
        permissions |= static_cast<unsigned int>(perm);
    }

    void revoke(Permission perm) {
        permissions &= ~static_cast<unsigned int>(perm);
    }

    bool has(Permission perm) const {
        return (permissions & static_cast<unsigned int>(perm)) != 0;
    }

    void display() const {
        std::cout << "Permissions: " << std::bitset<8>(permissions) << "\n";
        std::cout << "  Read: " << has(Permission::Read) << "\n";
        std::cout << "  Write: " << has(Permission::Write) << "\n";
        std::cout << "  Execute: " << has(Permission::Execute) << "\n";
        std::cout << "  Delete: " << has(Permission::Delete) << "\n";
    }
};

int main() {
    FilePermissions perms;

    perms.grant(Permission::Read);
    perms.grant(Permission::Write);
    perms.display();

    std::cout << "\nRevoking write permission:\n";
    perms.revoke(Permission::Write);
    perms.display();

    return 0;
}

Breaking It Down

Bitwise Operators

  • AND (&): Both bits must be 1. Use to check if a bit is set: flags & mask
  • OR (|): Either bit can be 1. Use to set a bit: flags | mask
  • XOR (^): Bits must differ. Use to toggle a bit: flags ^ mask
  • NOT (~): Flips all bits. Use with AND to clear a bit: flags & ~mask

Bit Shifts

  • Left shift (<<): Multiplies by powers of 2. 1 << 3 = 8 (binary 1000)
  • Right shift (>>): Divides by powers of 2. 16 >> 2 = 4
  • Use for flags: 1 << n creates a flag at position n
  • Remember: Shifting is faster than multiplication/division

Common Bit Patterns

  • Set bit: flags |= (1 << n) - turns bit n on
  • Clear bit: flags &= ~(1 << n) - turns bit n off
  • Toggle bit: flags ^= (1 << n) - flips bit n
  • Check bit: (flags & (1 << n)) != 0 - tests if bit n is set

Practical Applications

  • Flags: Store multiple booleans in one integer (32 flags in one int)
  • Permissions: Unix file permissions use 3 bits (read, write, execute)
  • Graphics: Pack RGBA colors into 32 bits (8 bits per channel)
  • Remember: Bits are space-efficient and cache-friendly

Why This Matters

  • Bit manipulation is the foundation of systems programming, graphics, cryptography, and optimization.
  • Operations on bits are the fastest instructions CPUs have - nothing is faster.
  • Use bits for flags (one integer can hold 32 boolean flags), permissions, network protocols, and compression.

Critical Insight

Bit manipulation is how computers really think! Everything in a computer is bits, and bitwise operations are the lowest-level, fastest operations the CPU can perform.

Think of an integer as 32 light switches. Bitwise operations let you flip individual switches without affecting the others. This is incredibly powerful: you can store 32 boolean flags in a single integer, saving memory and making your code faster because it fits in the CPU cache.

Unix file permissions (rwxrwxrwx) are a perfect example - 9 bits encode all the permission information. Network protocols, graphics, cryptography, and compression all rely heavily on bit manipulation. Once you master bits, you understand how computers work at the hardware level.

Best Practices

Use enum class for flags: Define bit positions with enum class for type safety and clarity.

Use bitset for visualization: std::bitset<N> converts integers to binary strings for debugging.

Document bit positions: Comment which bit represents what (e.g., bit 0 = Read, bit 1 = Write).

Use unsigned types: Bitwise operations work best with unsigned integers to avoid sign-extension issues.

Common Mistakes

Using = instead of |=: Assignment (=) overwrites all bits, losing previous flags. Use |= to set bits.

Signed integer shifts: Right-shifting signed integers can extend the sign bit, causing unexpected behavior.

Forgetting parentheses: Bitwise operators have lower precedence than comparisons. Use parentheses: if ((x & mask) != 0).

Off-by-one errors: Bit positions are zero-indexed. Bit 0 is the rightmost (least significant) bit.

Debug Challenge

This code tries to set a permission bit but has a bug. Click the highlighted line to fix it:

1 unsigned int permissions{0};
2
3 // Set the "write" permission (bit 1)
4 permissions = (1 << 1);
5
6 // Later: set "read" permission (bit 0)
7 permissions |= (1 << 0);

Quick Quiz

  1. What does (flags | mask) do?
Sets all bits in mask to 1 in flags
Clears all bits in mask from flags
Toggles all bits in mask
  1. How do you check if bit 3 is set in variable x?
(x & (1 << 3)) != 0
(x | (1 << 3)) != 0
(x ^ (1 << 3)) != 0
  1. What is the value of (1 << 4)?
16
4
8

Practice Playground

Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once