Advanced Topics
Bit Manipulation in C
Table of Contents
- •Introduction
- •Binary Number System
- •Bitwise Operators
- •Bit Shifting Operations
- •Common Bit Manipulation Techniques
- •Bit Fields in Structures
- •Bit Masks and Flags
- •Practical Applications
- •Performance Considerations
- •Common Pitfalls
- •Best Practices
- •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:
| A | B | A & B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
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:
| A | B | A | B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
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:
| A | B | A ^ B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
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
- •Binary System: Computers use binary (base-2) representation
- •Bitwise Operators: AND (&), OR (|), XOR (^), NOT (~), shifts (<<, >>)
- •Bit Manipulation: Setting, clearing, toggling, and checking bits
- •Bit Fields: Structure members with specified bit widths
- •Masks and Flags: Patterns for working with groups of bits
- •Applications: Colors, compression, networking, hardware access
Operator Quick Reference
| Operation | Operator | Example |
|---|---|---|
| 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
- •Use unsigned types for bit manipulation
- •Use explicit width types (uint8_t, uint32_t, etc.)
- •Document bit layouts clearly
- •Create helper macros for common operations
- •Be careful with operator precedence
- •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.