Docs

Advanced Topics

Bit Manipulation in C

Table of Contents

  1. Introduction
  2. Binary Number System
  3. Bitwise Operators
  4. Bit Shifting Operations
  5. Common Bit Manipulation Techniques
  6. Bit Fields in Structures
  7. Bit Masks and Flags
  8. Practical Applications
  9. Performance Considerations
  10. Common Pitfalls
  11. Best Practices
  12. Summary

Introduction

Bit manipulation is the act of algorithmically manipulating bits or other pieces of data shorter than a word. In C programming, bit manipulation provides:

  • Direct hardware control: Interface with registers and hardware devices
  • Memory efficiency: Pack multiple values into single variables
  • Performance optimization: Bit operations are often faster than arithmetic
  • Protocol implementation: Network protocols and file formats use bit-level data
  • Cryptography: Many encryption algorithms use bit manipulation extensively

Understanding bit manipulation is essential for:

  • Systems programming
  • Embedded development
  • Game development
  • Graphics programming
  • Network programming
  • Performance-critical applications

Binary Number System

Understanding Binary

Computers store all data in binary (base-2). Each digit is called a bit (binary digit) and can be either 0 or 1.

Decimal:  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
Binary:   0   1  10  11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111

Place Values

Each position in a binary number represents a power of 2:

Binary: 1    0    1    1    0    1    0    0
        ↓    ↓    ↓    ↓    ↓    ↓    ↓    ↓
Power:  2⁷   2⁶   2⁵   2⁴   2³   2²   2¹   2⁰
Value:  128  64   32   16   8    4    2    1

Result: 128 + 0 + 32 + 16 + 0 + 4 + 0 + 0 = 180

Hexadecimal Representation

Hexadecimal (base-16) is commonly used because each hex digit represents exactly 4 bits:

Binary:  0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
Hex:        0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F

C Integer Types and Bit Widths

Type                Typical Size    Range
char                8 bits          -128 to 127 (signed)
unsigned char       8 bits          0 to 255
short               16 bits         -32,768 to 32,767
unsigned short      16 bits         0 to 65,535
int                 32 bits         -2,147,483,648 to 2,147,483,647
unsigned int        32 bits         0 to 4,294,967,295
long long           64 bits         -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
unsigned long long  64 bits         0 to 18,446,744,073,709,551,615

Signed vs Unsigned

Unsigned integers use all bits for the magnitude:

unsigned char 255: 1111 1111
unsigned char 128: 1000 0000
unsigned char 0:   0000 0000

Signed integers use two's complement representation:

signed char 127:  0111 1111  (most positive)
signed char 0:    0000 0000
signed char -1:   1111 1111  (all bits set)
signed char -128: 1000 0000  (most negative)

Bitwise Operators

C provides six bitwise operators:

1. Bitwise AND (&)

Returns 1 only if both bits are 1.

A:     1010 1100
B:     1100 1010
A & B: 1000 1000

Truth Table:

ABA & B
000
010
100
111

Common Uses:

  • Clearing bits (masking)
  • Testing if a bit is set
  • Extracting specific bits
// Clear the lower 4 bits
value = value & 0xF0;

// Test if bit 3 is set
if (value & 0x08) { ... }

// Extract lower nibble
lower_nibble = value & 0x0F;

2. Bitwise OR (|)

Returns 1 if either bit is 1.

A:     1010 1100
B:     1100 1010
A | B: 1110 1110

Truth Table:

ABA | B
000
011
101
111

Common Uses:

  • Setting bits
  • Combining flags
  • Setting multiple bits at once
// Set bit 3
value = value | 0x08;

// Combine flags
flags = FLAG_READ | FLAG_WRITE;

3. Bitwise XOR (^)

Returns 1 if bits are different.

A:     1010 1100
B:     1100 1010
A ^ B: 0110 0110

Truth Table:

ABA ^ B
000
011
101
110

Common Uses:

  • Toggling bits
  • Swapping values without temporary
  • Simple encryption
  • Finding unique elements
// Toggle bit 3
value = value ^ 0x08;

// Swap without temp
a ^= b;
b ^= a;
a ^= b;

4. Bitwise NOT (~)

Inverts all bits (one's complement).

A:  1010 1100
~A: 0101 0011

Common Uses:

  • Creating masks
  • Clearing bits in combination with AND
// Clear bit 3 using NOT
value = value & ~0x08;

// Create mask for upper bits
upper_mask = ~0x0F;  // 0xF0

5. Left Shift (<<)

Shifts bits left, filling with zeros.

A:      0010 1100
A << 1: 0101 1000
A << 2: 1011 0000

Properties:

  • Left shift by n is equivalent to multiplying by 2^n
  • Bits shifted out are lost
  • New bits on the right are 0
// Multiply by 8
value = value << 3;  // value * 8

// Create bit at position n
bit_n = 1 << n;

6. Right Shift (>>)

Shifts bits right.

A:      1011 0000
A >> 1: 0101 1000 (unsigned) or 1101 1000 (signed, implementation-defined)
A >> 2: 0010 1100 (unsigned)

Properties:

  • Right shift by n is equivalent to dividing by 2^n (for unsigned)
  • For unsigned: zeros fill from left (logical shift)
  • For signed: behavior is implementation-defined (arithmetic or logical shift)
// Divide by 4
value = value >> 2;  // value / 4

// Extract upper nibble
upper = value >> 4;

Bit Shifting Operations

Rotation Operations

C doesn't have rotation operators, but they can be implemented:

// Rotate left
uint32_t rotate_left(uint32_t value, int count) {
    return (value << count) | (value >> (32 - count));
}

// Rotate right
uint32_t rotate_right(uint32_t value, int count) {
    return (value >> count) | (value << (32 - count));
}

Circular Shifts

// Rotate byte left by 1
unsigned char rol8(unsigned char value) {
    return (value << 1) | (value >> 7);
}

// Rotate byte right by 1
unsigned char ror8(unsigned char value) {
    return (value >> 1) | (value << 7);
}

Barrel Shifter Patterns

// Variable rotation (modulo width)
uint32_t rotate_left_32(uint32_t x, uint32_t n) {
    n &= 31;  // Limit to 0-31
    return (x << n) | (x >> (32 - n));
}

Common Bit Manipulation Techniques

Setting a Bit

// Set bit n to 1
value = value | (1 << n);
value |= (1 << n);  // Shorthand

// Example: Set bit 3
value |= (1 << 3);  // value |= 0x08

Clearing a Bit

// Clear bit n to 0
value = value & ~(1 << n);
value &= ~(1 << n);  // Shorthand

// Example: Clear bit 3
value &= ~(1 << 3);  // value &= 0xF7

Toggling a Bit

// Toggle bit n
value = value ^ (1 << n);
value ^= (1 << n);  // Shorthand

// Example: Toggle bit 3
value ^= (1 << 3);

Checking a Bit

// Check if bit n is set
bool is_set = (value >> n) & 1;
// or
bool is_set = (value & (1 << n)) != 0;

// Example: Check bit 3
if (value & (1 << 3)) {
    printf("Bit 3 is set\n");
}

Setting/Clearing Multiple Bits

// Set bits 0, 2, and 4
value |= (1 << 0) | (1 << 2) | (1 << 4);  // value |= 0x15

// Clear bits 1, 3, and 5
value &= ~((1 << 1) | (1 << 3) | (1 << 5));  // value &= ~0x2A

Extracting Bit Fields

// Extract bits from position 'start' with 'length' bits
unsigned extract_bits(unsigned value, int start, int length) {
    unsigned mask = (1U << length) - 1;  // Create mask of 'length' 1s
    return (value >> start) & mask;
}

// Example: Extract bits 4-7 (4 bits starting at position 4)
unsigned nibble = extract_bits(0xAB, 4, 4);  // Returns 0xA

Inserting Bit Fields

// Insert 'field' at position 'start' with 'length' bits
unsigned insert_bits(unsigned value, unsigned field, int start, int length) {
    unsigned mask = ((1U << length) - 1) << start;
    return (value & ~mask) | ((field << start) & mask);
}

// Example: Insert 0xC at bits 4-7
unsigned result = insert_bits(0x12, 0xC, 4, 4);  // Returns 0xC2

Counting Set Bits (Population Count)

// Brian Kernighan's algorithm
int count_set_bits(unsigned int n) {
    int count = 0;
    while (n) {
        n &= (n - 1);  // Clear lowest set bit
        count++;
    }
    return count;
}

// Lookup table method (faster for 8-bit)
int count_bits_table(unsigned char n) {
    static const int table[16] = {
        0, 1, 1, 2, 1, 2, 2, 3,
        1, 2, 2, 3, 2, 3, 3, 4
    };
    return table[n & 0xF] + table[n >> 4];
}

Finding Lowest/Highest Set Bit

// Find position of lowest set bit (returns -1 if no bit set)
int find_lowest_set_bit(unsigned int n) {
    if (n == 0) return -1;
    int position = 0;
    while ((n & 1) == 0) {
        n >>= 1;
        position++;
    }
    return position;
}

// Isolate lowest set bit
unsigned int isolate_lowest_bit(unsigned int n) {
    return n & (-n);  // Works due to two's complement
}

// Clear lowest set bit
unsigned int clear_lowest_bit(unsigned int n) {
    return n & (n - 1);
}

// Find position of highest set bit
int find_highest_set_bit(unsigned int n) {
    if (n == 0) return -1;
    int position = 0;
    while (n >>= 1) {
        position++;
    }
    return position;
}

Power of 2 Operations

// Check if n is a power of 2
bool is_power_of_2(unsigned int n) {
    return n && !(n & (n - 1));
}

// Round up to next power of 2
unsigned int next_power_of_2(unsigned int n) {
    n--;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    return n + 1;
}

// Round down to previous power of 2
unsigned int prev_power_of_2(unsigned int n) {
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    return n - (n >> 1);
}

Bit Reversal

// Reverse bits in a byte
unsigned char reverse_byte(unsigned char b) {
    b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
    b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
    b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
    return b;
}

// Reverse bits in 32-bit integer
uint32_t reverse_bits_32(uint32_t n) {
    n = ((n >> 1) & 0x55555555) | ((n & 0x55555555) << 1);
    n = ((n >> 2) & 0x33333333) | ((n & 0x33333333) << 2);
    n = ((n >> 4) & 0x0F0F0F0F) | ((n & 0x0F0F0F0F) << 4);
    n = ((n >> 8) & 0x00FF00FF) | ((n & 0x00FF00FF) << 8);
    n = (n >> 16) | (n << 16);
    return n;
}

Bit Fields in Structures

C allows defining structure members with specific bit widths:

Basic Syntax

struct BitFields {
    unsigned int flag1 : 1;    // 1 bit
    unsigned int flag2 : 1;    // 1 bit
    unsigned int value : 4;    // 4 bits (0-15)
    unsigned int state : 3;    // 3 bits (0-7)
    unsigned int data : 8;     // 8 bits (0-255)
};

Complete Example

struct PacketHeader {
    unsigned int version : 4;      // IP version (4 bits)
    unsigned int header_length : 4; // Header length (4 bits)
    unsigned int tos : 8;          // Type of service (8 bits)
    unsigned int total_length : 16; // Total length (16 bits)
};

// Usage
struct PacketHeader header;
header.version = 4;
header.header_length = 5;
header.tos = 0;
header.total_length = 1500;

Bit Field Considerations

Advantages:

  • Memory efficient
  • Self-documenting code
  • Compiler handles bit manipulation

Disadvantages:

  • Not portable (implementation-defined behavior)
  • Cannot take address of bit field
  • Performance may vary
  • Memory layout is compiler-dependent
// Cannot do this:
struct BitField {
    unsigned int flag : 1;
};
struct BitField bf;
int *p = &bf.flag;  // ERROR: Cannot take address of bit field

// Bit fields may not span storage units (implementation-defined)
struct Spanning {
    unsigned int a : 24;
    unsigned int b : 16;  // May start new storage unit
};

Unnamed Bit Fields

struct Flags {
    unsigned int visible : 1;
    unsigned int : 0;          // Force alignment to next unit
    unsigned int enabled : 1;
    unsigned int : 3;          // Skip 3 bits (padding)
    unsigned int mode : 2;
};

Bit Masks and Flags

Flag Definitions

// Using bit positions
#define FLAG_READ     (1 << 0)   // 0x01
#define FLAG_WRITE    (1 << 1)   // 0x02
#define FLAG_EXECUTE  (1 << 2)   // 0x04
#define FLAG_DELETE   (1 << 3)   // 0x08
#define FLAG_HIDDEN   (1 << 4)   // 0x10
#define FLAG_SYSTEM   (1 << 5)   // 0x20

// Using hexadecimal (equivalent)
#define FLAG_READ     0x01
#define FLAG_WRITE    0x02
#define FLAG_EXECUTE  0x04
#define FLAG_DELETE   0x08
#define FLAG_HIDDEN   0x10
#define FLAG_SYSTEM   0x20

// Combination flags
#define FLAG_RW       (FLAG_READ | FLAG_WRITE)
#define FLAG_RWX      (FLAG_READ | FLAG_WRITE | FLAG_EXECUTE)
#define FLAG_ALL      0x3F

Flag Operations

typedef unsigned int Flags;

// Set flags
Flags permissions = 0;
permissions |= FLAG_READ | FLAG_WRITE;

// Check flags
if (permissions & FLAG_READ) {
    printf("Read access granted\n");
}

// Check multiple flags (all must be set)
if ((permissions & FLAG_RWX) == FLAG_RWX) {
    printf("Full access\n");
}

// Check if any flag is set
if (permissions & (FLAG_WRITE | FLAG_DELETE)) {
    printf("Has write or delete\n");
}

// Clear flags
permissions &= ~FLAG_WRITE;

// Toggle flags
permissions ^= FLAG_EXECUTE;

// Clear all flags
permissions = 0;

Type-Safe Flags with Enums

typedef enum {
    PERM_NONE    = 0,
    PERM_READ    = (1 << 0),
    PERM_WRITE   = (1 << 1),
    PERM_EXECUTE = (1 << 2),
    PERM_DELETE  = (1 << 3),
    PERM_ALL     = PERM_READ | PERM_WRITE | PERM_EXECUTE | PERM_DELETE
} Permission;

void check_permissions(Permission p) {
    if (p & PERM_READ)    printf("Can read\n");
    if (p & PERM_WRITE)   printf("Can write\n");
    if (p & PERM_EXECUTE) printf("Can execute\n");
    if (p & PERM_DELETE)  printf("Can delete\n");
}

Mask Patterns

// Byte masks
#define BYTE_MASK       0xFF
#define WORD_MASK       0xFFFF
#define DWORD_MASK      0xFFFFFFFF

// Nibble masks
#define LOW_NIBBLE      0x0F
#define HIGH_NIBBLE     0xF0

// Sign bit
#define SIGN_BIT_8      0x80
#define SIGN_BIT_16     0x8000
#define SIGN_BIT_32     0x80000000

// Common patterns
#define ALTERNATING_1   0x55555555  // 01010101...
#define ALTERNATING_2   0xAAAAAAAA  // 10101010...

Practical Applications

1. RGB Color Manipulation

typedef uint32_t Color;  // ARGB format

// Extract components
uint8_t get_alpha(Color c) { return (c >> 24) & 0xFF; }
uint8_t get_red(Color c)   { return (c >> 16) & 0xFF; }
uint8_t get_green(Color c) { return (c >> 8) & 0xFF; }
uint8_t get_blue(Color c)  { return c & 0xFF; }

// Create color
Color make_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
    return ((Color)a << 24) | ((Color)r << 16) |
           ((Color)g << 8) | (Color)b;
}

// Blend colors
Color blend_colors(Color c1, Color c2) {
    uint8_t r = (get_red(c1) + get_red(c2)) / 2;
    uint8_t g = (get_green(c1) + get_green(c2)) / 2;
    uint8_t b = (get_blue(c1) + get_blue(c2)) / 2;
    uint8_t a = (get_alpha(c1) + get_alpha(c2)) / 2;
    return make_color(r, g, b, a);
}

2. Compact Boolean Array

typedef struct {
    unsigned char *bits;
    size_t size;
} BitArray;

BitArray* bitarray_create(size_t num_bits) {
    BitArray *ba = malloc(sizeof(BitArray));
    ba->size = num_bits;
    ba->bits = calloc((num_bits + 7) / 8, 1);
    return ba;
}

void bitarray_set(BitArray *ba, size_t index) {
    ba->bits[index / 8] |= (1 << (index % 8));
}

void bitarray_clear(BitArray *ba, size_t index) {
    ba->bits[index / 8] &= ~(1 << (index % 8));
}

bool bitarray_get(BitArray *ba, size_t index) {
    return (ba->bits[index / 8] >> (index % 8)) & 1;
}

void bitarray_toggle(BitArray *ba, size_t index) {
    ba->bits[index / 8] ^= (1 << (index % 8));
}

3. Network Byte Order Conversion

// Convert between host and network byte order
uint16_t swap_bytes_16(uint16_t value) {
    return (value << 8) | (value >> 8);
}

uint32_t swap_bytes_32(uint32_t value) {
    return ((value & 0xFF000000) >> 24) |
           ((value & 0x00FF0000) >> 8) |
           ((value & 0x0000FF00) << 8) |
           ((value & 0x000000FF) << 24);
}

// Check endianness
bool is_little_endian(void) {
    int i = 1;
    return *((char*)&i) == 1;
}

4. Checksum Calculation

// Simple XOR checksum
uint8_t xor_checksum(const uint8_t *data, size_t len) {
    uint8_t checksum = 0;
    for (size_t i = 0; i < len; i++) {
        checksum ^= data[i];
    }
    return checksum;
}

// CRC-8 (polynomial: x^8 + x^2 + x + 1)
uint8_t crc8(const uint8_t *data, size_t len) {
    uint8_t crc = 0;
    for (size_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 0x80) {
                crc = (crc << 1) ^ 0x07;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}

5. Hardware Register Access

// Volatile for hardware registers
#define REGISTER_BASE   ((volatile uint32_t*)0x40000000)
#define CONTROL_REG     (REGISTER_BASE[0])
#define STATUS_REG      (REGISTER_BASE[1])

// Control register bits
#define CTRL_ENABLE     (1 << 0)
#define CTRL_INTERRUPT  (1 << 1)
#define CTRL_DIRECTION  (1 << 2)
#define CTRL_SPEED_MASK (0x3 << 4)
#define CTRL_SPEED_POS  4

// Set control bits
void enable_device(void) {
    CONTROL_REG |= CTRL_ENABLE | CTRL_INTERRUPT;
}

// Set speed (2-bit field at position 4)
void set_speed(int speed) {
    uint32_t reg = CONTROL_REG;
    reg &= ~CTRL_SPEED_MASK;              // Clear speed bits
    reg |= (speed << CTRL_SPEED_POS);     // Set new speed
    CONTROL_REG = reg;
}

Performance Considerations

Multiplication and Division by Powers of 2

// Multiplication
x * 2   →  x << 1
x * 4   →  x << 2
x * 8   →  x << 3
x * 16  →  x << 4

// Division (for positive numbers)
x / 2   →  x >> 1
x / 4   →  x >> 2
x / 8   →  x >> 3

// Modulo for powers of 2
x % 8   →  x & 7
x % 16  →  x & 15
x % 32  →  x & 31

Conditional Without Branches

// Absolute value without branch
int abs_no_branch(int x) {
    int mask = x >> 31;  // All 0s if positive, all 1s if negative
    return (x + mask) ^ mask;
}

// Min/Max without branch
int min_no_branch(int a, int b) {
    return b + ((a - b) & ((a - b) >> 31));
}

int max_no_branch(int a, int b) {
    return a - ((a - b) & ((a - b) >> 31));
}

// Conditional set
int conditional_set(int condition, int value) {
    return (-condition) & value;  // Returns value if condition is non-zero
}

Loop Optimizations

// Check if any bit is set in range
bool any_bit_set(uint32_t value, int start, int count) {
    uint32_t mask = ((1U << count) - 1) << start;
    return (value & mask) != 0;
}

// Clear lowest N bits
uint32_t clear_lowest_n_bits(uint32_t value, int n) {
    uint32_t mask = ~((1U << n) - 1);
    return value & mask;
}

Common Pitfalls

1. Signed Right Shift

// Behavior is implementation-defined for signed integers!
int x = -8;
int y = x >> 1;  // May be -4 (arithmetic) or a large positive (logical)

// Solution: Use unsigned types
unsigned int ux = x;
unsigned int uy = ux >> 1;  // Always logical shift

2. Shift Amount Too Large

int x = 1;
int y = x << 32;  // Undefined behavior if int is 32 bits!
int z = x << 33;  // Undefined behavior!

// Solution: Limit shift amount
int safe_shift(int x, int n) {
    if (n >= (int)(sizeof(int) * 8)) return 0;
    return x << n;
}

3. Operator Precedence

// Common mistakes
if (x & 1 == 1) { }  // Wrong! Parses as x & (1 == 1)
if ((x & 1) == 1) { }  // Correct

int y = x & 3 + 1;  // Wrong! Parses as x & (3 + 1)
int y = (x & 3) + 1;  // Correct

4. Sign Extension

// When converting from smaller to larger type
signed char c = -1;  // 0xFF
int i = c;           // 0xFFFFFFFF (sign extended)

// Solution for unsigned extension
unsigned char uc = 0xFF;
unsigned int ui = uc;  // 0x000000FF (zero extended)

5. Mixing Signed and Unsigned

unsigned int u = 0;
int s = -1;

if (s < u) {
    // This may not execute!
    // s is converted to unsigned, becoming a large positive number
}

Best Practices

1. Use Explicit Types

#include <stdint.h>

// Use fixed-width types for portability
uint8_t byte_value;
uint16_t word_value;
uint32_t dword_value;
uint64_t qword_value;

// Use unsigned for bit manipulation
unsigned int flags = 0;

2. Document Bit Layouts

/**
 * Packet format (32 bits):
 * Bits 31-28: Version (4 bits)
 * Bits 27-24: Type (4 bits)
 * Bits 23-16: Sequence (8 bits)
 * Bits 15-0:  Payload length (16 bits)
 */
#define PKT_VERSION_SHIFT   28
#define PKT_VERSION_MASK    0xF0000000
#define PKT_TYPE_SHIFT      24
#define PKT_TYPE_MASK       0x0F000000
#define PKT_SEQ_SHIFT       16
#define PKT_SEQ_MASK        0x00FF0000
#define PKT_LEN_MASK        0x0000FFFF

3. Create Helper Macros

// Generic bit manipulation macros
#define BIT(n)              (1UL << (n))
#define SET_BIT(x, n)       ((x) |= BIT(n))
#define CLEAR_BIT(x, n)     ((x) &= ~BIT(n))
#define TOGGLE_BIT(x, n)    ((x) ^= BIT(n))
#define CHECK_BIT(x, n)     (((x) >> (n)) & 1)

// Field extraction macros
#define GET_FIELD(x, mask, shift)   (((x) & (mask)) >> (shift))
#define SET_FIELD(x, val, mask, shift) \
    ((x) = ((x) & ~(mask)) | (((val) << (shift)) & (mask)))

4. Use Constants for Magic Numbers

// Bad
value = value & 0x0F;
value = value | 0x80;

// Good
#define LOWER_NIBBLE_MASK  0x0F
#define MSB_FLAG           0x80

value = value & LOWER_NIBBLE_MASK;
value = value | MSB_FLAG;

5. Use Static Analysis and Testing

// Add assertions for invariants
#include <assert.h>

void set_nibble(uint8_t *byte, uint8_t nibble, bool upper) {
    assert(nibble <= 0x0F);  // Nibble must be 4 bits

    if (upper) {
        *byte = (*byte & 0x0F) | (nibble << 4);
    } else {
        *byte = (*byte & 0xF0) | nibble;
    }
}

Summary

Key Concepts

  1. Binary System: Computers use binary (base-2) representation
  2. Bitwise Operators: AND (&), OR (|), XOR (^), NOT (~), shifts (<<, >>)
  3. Bit Manipulation: Setting, clearing, toggling, and checking bits
  4. Bit Fields: Structure members with specified bit widths
  5. Masks and Flags: Patterns for working with groups of bits
  6. Applications: Colors, compression, networking, hardware access

Operator Quick Reference

OperationOperatorExample
AND&a & b
OR|a | b
XOR^a ^ b
NOT~~a
Left Shift<<a << n
Right Shift>>a >> n

Common Patterns

// Set bit n
x |= (1 << n);

// Clear bit n
x &= ~(1 << n);

// Toggle bit n
x ^= (1 << n);

// Check bit n
if (x & (1 << n)) { ... }

// Extract bits
field = (x >> start) & ((1 << length) - 1);

Best Practices Summary

  1. Use unsigned types for bit manipulation
  2. Use explicit width types (uint8_t, uint32_t, etc.)
  3. Document bit layouts clearly
  4. Create helper macros for common operations
  5. Be careful with operator precedence
  6. Test thoroughly, especially edge cases

Bit manipulation is a fundamental skill for systems programming, enabling efficient and direct interaction with data at its most basic level.

Advanced Topics - C Programming Tutorial | DeepML