|$ curl https://forge-ai.dev/api/markdown?path=docs/c/bitwise
$cat docs/c-—-bitwise-operations.md
updated Recently·40 min read·published

C — Bitwise Operations

CBitwiseBinaryAdvancedAdvanced🎯Free Tools
Binary Number System Review

Binary (base-2) uses only 0 and 1. Each bit position represents a power of 2. Understanding binary is essential for bitwise operations. A byte (8 bits) can represent values 0–255. Bitwise operations work directly on the binary representation of integers.

binary_review.c
C
1#include <stdio.h>
2
3// Print binary representation of an integer
4void print_binary(unsigned int n) {
5 for (int i = sizeof(n) * 8 - 1; i >= 0; i--) {
6 putchar((n >> i) & 1 ? '1' : '0');
7 if (i % 8 == 0 && i > 0) putchar(' ');
8 }
9}
10
11int main(void) {
12 unsigned int a = 42; // Binary: 00101010
13 unsigned int b = 15; // Binary: 00001111
14
15 printf("a = "); print_binary(a); printf(" (%u)\n", a);
16 printf("b = "); print_binary(b); printf(" (%u)\n", b);
17
18 // Bit positions: bit 0 is the rightmost (least significant) bit
19 // 42 = 32 + 8 + 2 = 2^5 + 2^3 + 2^1
20 // 00101010
21 // ||| | |
22 // ||| | +-- bit 1 (2^1 = 2)
23 // ||| +---- bit 3 (2^3 = 8)
24 // ||+------ bit 5 (2^5 = 32)
25
26 return 0;
27}
Bitwise AND (&): Masking and Extracting

The AND operator returns 1 only where both bits are 1. It is used for masking: extracting specific bits from a value by AND-ing with a mask.

bitwise_and.c
C
1#include <stdio.h>
2
3void print_binary_8(unsigned char n) {
4 for (int i = 7; i >= 0; i--) {
5 putchar((n >> i) & 1 ? '1' : '0');
6 }
7}
8
9int main(void) {
10 unsigned char a = 0b11010110; // 214
11 unsigned char b = 0b10101010; // 170
12
13 // AND truth table: both must be 1
14 // 1 & 1 = 1, 1 & 0 = 0, 0 & 1 = 0, 0 & 0 = 0
15
16 unsigned char result = a & b;
17 printf("a = "); print_binary_8(a); printf("\n");
18 printf("b = "); print_binary_8(b); printf("\n");
19 printf("a & b = "); print_binary_8(result); printf("\n");
20 // Result: 10000010 (130)
21
22 // Masking: extract lower nibble (bits 0-3)
23 unsigned char value = 0xCD; // 11001101
24 unsigned char lower = value & 0x0F; // 00001101 = 13
25 unsigned char upper = value & 0xF0; // 11000000 = 192
26
27 printf("\nvalue = "); print_binary_8(value); printf("\n");
28 printf("lower nib = "); print_binary_8(lower); printf(" (%u)\n", lower);
29 printf("upper nib = "); print_binary_8(upper); printf(" (%u)\n", upper);
30
31 // Check if a specific bit is set (bit 3)
32 int bit3 = (value >> 3) & 1;
33 printf("Bit 3 is %s\n", bit3 ? "set" : "clear");
34
35 // Check if number is even (bit 0 is 0)
36 unsigned int num = 42;
37 printf("%u is %s\n", num, (num & 1) ? "odd" : "even");
38
39 return 0;
40}
Bitwise OR (|): Setting Bits and Combining Flags

The OR operator returns 1 where at least one bit is 1. It is used to set specific bits in a value, commonly used for combining permission flags.

bitwise_or.c
C
1#include <stdio.h>
2
3// Permission flags
4#define PERM_READ (1 << 0) // 0001 = 1
5#define PERM_WRITE (1 << 1) // 0010 = 2
6#define PERM_EXECUTE (1 << 2) // 0100 = 4
7#define PERM_ADMIN (1 << 3) // 1000 = 8
8
9const char *perm_name(int perm) {
10 switch (perm) {
11 case PERM_READ: return "READ";
12 case PERM_WRITE: return "WRITE";
13 case PERM_EXECUTE: return "EXECUTE";
14 case PERM_ADMIN: return "ADMIN";
15 default: return "UNKNOWN";
16 }
17}
18
19void print_permissions(int flags) {
20 printf("Permissions: 0x%X [", flags);
21 int first = 1;
22 if (flags & PERM_READ) { if (!first) printf(", "); printf("READ"); first = 0; }
23 if (flags & PERM_WRITE) { if (!first) printf(", "); printf("WRITE"); first = 0; }
24 if (flags & PERM_EXECUTE) { if (!first) printf(", "); printf("EXECUTE"); first = 0; }
25 if (flags & PERM_ADMIN) { if (!first) printf(", "); printf("ADMIN"); first = 0; }
26 printf("]\n");
27}
28
29int main(void) {
30 int perms = 0; // No permissions
31
32 // Set bits using OR
33 perms |= PERM_READ;
34 perms |= PERM_WRITE;
35 print_permissions(perms); // READ, WRITE
36
37 // Set multiple bits at once
38 perms = PERM_READ | PERM_EXECUTE;
39 print_permissions(perms); // READ, EXECUTE
40
41 // OR truth table: at least one must be 1
42 unsigned char a = 0b11000000;
43 unsigned char b = 0b00110000;
44 unsigned char c = a | b; // 0b11110000
45
46 printf("\na = 0x%02X\n", a);
47 printf("b = 0x%02X\n", b);
48 printf("a | b = 0x%02X\n", c);
49
50 return 0;
51}
Bitwise XOR (^): Toggling and the XOR Swap

XOR returns 1 where bits differ. It toggles specific bits when used with a mask. XOR also has the unique property that A ^ A = 0 and A ^ 0 = A, enabling the famous XOR swap trick.

bitwise_xor.c
C
1#include <stdio.h>
2
3int main(void) {
4 // XOR truth table: bits must differ
5 // 1 ^ 1 = 0, 1 ^ 0 = 1, 0 ^ 1 = 1, 0 ^ 0 = 0
6
7 unsigned char a = 0b11010110;
8 unsigned char b = 0b10101010;
9
10 printf("a = 0x%02X\n", a);
11 printf("b = 0x%02X\n", b);
12 printf("a ^ b = 0x%02X\n", a ^ b); // 0x7C
13
14 // Toggle bits: XOR with 1 flips, XOR with 0 leaves unchanged
15 unsigned char val = 0b10101010;
16 unsigned char mask = 0b00001111; // Toggle lower nibble
17 val ^= mask;
18 printf("\nToggled: 0x%02X\n", val); // 0xB5
19
20 // XOR swap (no temporary variable needed)
21 int x = 42, y = 99;
22 printf("\nBefore: x=%d, y=%d\n", x, y);
23 x ^= y; // x = x ^ y
24 y ^= x; // y = y ^ (x ^ y) = x
25 x ^= y; // x = (x ^ y) ^ x = y
26 printf("After: x=%d, y=%d\n", x, y);
27
28 // XOR properties for verification
29 int v = 123;
30 int encoded = v ^ 0xA5; // Encode
31 int decoded = encoded ^ 0xA5; // Decode (same key)
32 printf("\nOriginal: %d, Encoded: %d, Decoded: %d\n", v, encoded, decoded);
33
34 // Parity check: count set bits mod 2
35 unsigned char byte = 0b11010110;
36 unsigned char parity = byte;
37 parity ^= parity >> 4;
38 parity ^= parity >> 2;
39 parity ^= parity >> 1;
40 printf("\nParity of 0x%02X: %d\n", byte, parity & 1);
41
42 return 0;
43}

warning

The XOR swap trick is a clever demonstration but should not be used in production code. It is slower than using a temporary variable on modern CPUs due to data dependencies that prevent instruction-level parallelism.
Bitwise NOT (~): One's Complement

The NOT operator flips every bit: 0 becomes 1 and 1 becomes 0. For unsigned types, ~n equals MAX_VALUE - n. For signed types, ~n equals -n - 1.

bitwise_not.c
C
1#include <stdio.h>
2
3int main(void) {
4 unsigned char a = 0b00001111; // 15
5 unsigned char b = ~a; // 0b11110000 = 240
6
7 printf("a = 0x%02X (%u)\n", a, a);
8 printf("~a = 0x%02X (%u)\n", b, b);
9
10 // For unsigned: ~n = MAX - n
11 unsigned int x = 42;
12 printf("\n~%u = %u\n", x, ~x); // ~42 = 4294967253
13
14 // For signed: ~n = -n - 1
15 int y = 42;
16 printf("~%d = %d\n", y, ~y); // ~42 = -43
17
18 // Creating a mask of all 1s
19 unsigned int all_ones = ~0u; // 0xFFFFFFFF
20 printf("\nAll ones: 0x%08X\n", all_ones);
21
22 // Masking out upper bits: keep only lower n bits
23 unsigned int value = 0xDEADBEEF;
24 unsigned int lower_16 = value & (~0u >> 16); // 0x0000BEEF
25 printf("Lower 16 bits: 0x%04X\n", lower_16);
26
27 // Double complement: ~~n == n (for unsigned)
28 unsigned char c = 0b10101010;
29 unsigned char d = ~~c;
30 printf("\n~~0x%02X = 0x%02X\n", c, d);
31
32 return 0;
33}
Left Shift (<<): Multiplying by Powers of 2

Left shifting by n bits multiplies the value by 2^n. It moves all bits to the left, filling the right with zeros. Left shift is well-defined for unsigned types.

left_shift.c
C
1#include <stdio.h>
2
3int main(void) {
4 unsigned int a = 1;
5
6 // Left shift = multiply by 2^n
7 printf("1 << 0 = %u\n", a << 0); // 1
8 printf("1 << 1 = %u\n", a << 1); // 2
9 printf("1 << 2 = %u\n", a << 2); // 4
10 printf("1 << 3 = %u\n", a << 3); // 8
11 printf("1 << 10 = %u\n", a << 10); // 1024
12 printf("1 << 31 = %u\n", a << 31); // 2147483648
13
14 // Practical: calculate powers of 2
15 for (int i = 0; i < 10; i++) {
16 printf("2^%d = %u\n", i, 1u << i);
17 }
18
19 // Shift amount must be < bit width (undefined behavior otherwise)
20 int val = 5;
21 // val << 32 // UNDEFINED BEHAVIOR on 32-bit int!
22 // val << -1 // UNDEFINED BEHAVIOR!
23
24 // Left shift with unsigned is always well-defined
25 unsigned int safe = 5u;
26 printf("\nSafe shift: %u\n", safe << 28); // Well-defined
27
28 // Shifting signed negative values is implementation-defined
29 int neg = -1;
30 printf("Left shift -1: %d\n", neg << 1); // Implementation-defined
31
32 return 0;
33}
Right Shift (>>): Dividing by Powers of 2

Right shifting by n divides by 2^n. For unsigned types, zeros fill from the left. For signed types, the behavior depends on the implementation: arithmetic shift (sign-extended) or logical shift (zero-filled). C23 mandates arithmetic shift.

right_shift.c
C
1#include <stdio.h>
2
3int main(void) {
4 // Unsigned right shift: always zero-filled
5 unsigned int a = 64;
6 printf("64 >> 0 = %u\n", a >> 0); // 64
7 printf("64 >> 1 = %u\n", a >> 1); // 32
8 printf("64 >> 2 = %u\n", a >> 2); // 16
9 printf("64 >> 6 = %u\n", a >> 6); // 1
10
11 // Signed right shift: implementation-defined (usually arithmetic)
12 int pos = 64;
13 int neg = -64;
14
15 printf("\nSigned positive:\n");
16 printf(" 64 >> 1 = %d\n", pos >> 1); // 32
17
18 printf("Signed negative (arithmetic shift on most systems):\n");
19 printf(" -64 >> 1 = %d\n", neg >> 1); // -32 (arithmetic)
20 printf(" -64 >> 2 = %d\n", neg >> 2); // -16
21
22 // Division by 2 (unsigned)
23 unsigned int dividend = 100;
24 printf("\n100 / 2 = %u (division)\n", dividend / 2);
25 printf("100 >> 1 = %u (shift)\n", dividend >> 1);
26
27 // Integer division truncates toward zero for positive numbers
28 printf("7 >> 1 = %d (not 3.5!)\n", 7 >> 1);
29
30 // CRITICAL: shift by >= bit width is UNDEFINED BEHAVIOR
31 unsigned int x = 1;
32 // x >> 32 // UB on 32-bit unsigned!
33 // x << 32 // UB on 32-bit unsigned!
34
35 return 0;
36}

warning

Shifting a value by a number of bits greater than or equal to the bit width of the type is undefined behavior in C. Always ensure shift amounts are in the range [0, bit_width - 1].
Bit Masks: Creating Masks for Specific Bits

Bit manipulation fundamentals: setting, clearing, toggling, and checking individual bits. These operations form the foundation of embedded systems, networking, and file permission code.

bit_masks.c
C
1#include <stdio.h>
2
3// Set bit n (make it 1)
4#define SET_BIT(flags, n) ((flags) |= (1u << (n)))
5
6// Clear bit n (make it 0)
7#define CLEAR_BIT(flags, n) ((flags) &= ~(1u << (n)))
8
9// Toggle bit n (flip it)
10#define TOGGLE_BIT(flags, n) ((flags) ^= (1u << (n)))
11
12// Check bit n (returns 0 or 1)
13#define CHECK_BIT(flags, n) (((flags) >> (n)) & 1u)
14
15// Extract bits [start, start+width)
16#define EXTRACT_BITS(val, start, width) \
17 (((val) >> (start)) & ((1u << (width)) - 1))
18
19// Replace bits [start, start+width)
20#define REPLACE_BITS(val, start, width, new_val) \
21 (((val) & ~(((1u << (width)) - 1) << (start))) | \
22 (((new_val) & ((1u << (width)) - 1)) << (start)))
23
24void print_bin(unsigned int n, int bits) {
25 for (int i = bits - 1; i >= 0; i--)
26 putchar((n >> i) & 1 ? '1' : '0');
27}
28
29int main(void) {
30 unsigned int flags = 0;
31
32 // Set bits 0, 3, 5
33 SET_BIT(flags, 0);
34 SET_BIT(flags, 3);
35 SET_BIT(flags, 5);
36 printf("After set: "); print_bin(flags, 8); printf("\n");
37
38 // Clear bit 3
39 CLEAR_BIT(flags, 3);
40 printf("After clear: "); print_bin(flags, 8); printf("\n");
41
42 // Toggle bit 5
43 TOGGLE_BIT(flags, 5);
44 printf("After toggle:"); print_bin(flags, 8); printf("\n");
45
46 // Check bits
47 printf("Bit 0: %u\n", CHECK_BIT(flags, 0));
48 printf("Bit 3: %u\n", CHECK_BIT(flags, 3));
49
50 // Extract a field: bits 4-7 (4 bits wide)
51 unsigned int reg = 0xABCD;
52 unsigned int field = EXTRACT_BITS(reg, 4, 4);
53 printf("\nReg: 0x%04X\n", reg);
54 printf("Bits [4:7]: 0x%X\n", field);
55
56 // Replace bits 0-3 with 0x5
57 unsigned int new_reg = REPLACE_BITS(reg, 0, 4, 0x5);
58 printf("After replace: 0x%04X\n", new_reg);
59
60 return 0;
61}
Extracting Bit Fields

Extracting a range of bits is a common operation in parsing binary formats, network protocols, and hardware registers. The pattern is: shift right to align the field to bit 0, then mask to keep only the desired width.

extract_bits.c
C
1#include <stdio.h>
2
3// Extract bits [start, start+width) from value
4unsigned int extract_bits(unsigned int value, int start, int width) {
5 unsigned int mask = (1u << width) - 1; // Create width-bit mask
6 return (value >> start) & mask;
7}
8
9// Set bits [start, start+width) to new_val in value
10unsigned int set_bits(unsigned int value, int start, int width,
11 unsigned int new_val) {
12 unsigned int mask = ((1u << width) - 1) << start;
13 return (value & ~mask) | ((new_val << start) & mask);
14}
15
16int main(void) {
17 // Example: parsing an IPv4 address from a 32-bit integer
18 unsigned int ip = 0xC0A80101; // 192.168.1.1
19
20 unsigned int octet3 = extract_bits(ip, 24, 8); // 192
21 unsigned int octet2 = extract_bits(ip, 16, 8); // 168
22 unsigned int octet1 = extract_bits(ip, 8, 8); // 1
23 unsigned int octet0 = extract_bits(ip, 0, 8); // 1
24
25 printf("IP: %u.%u.%u.%u\n", octet3, octet2, octet1, octet0);
26
27 // Example: parsing RGB565 color (16-bit: RRRRRGGGGGGBBBBB)
28 unsigned short rgb565 = 0xF81F; // Red
29 unsigned int r5 = extract_bits(rgb565, 11, 5);
30 unsigned int g6 = extract_bits(rgb565, 5, 6);
31 unsigned int b5 = extract_bits(rgb565, 0, 5);
32
33 printf("\nRGB565 0x%04X -> R:%u G:%u B:%u\n", rgb565, r5, g6, b5);
34
35 // Scale to 8-bit: multiply by 255 and divide by max value
36 unsigned int r8 = (r5 * 255) / 31;
37 unsigned int g8 = (g6 * 255) / 63;
38 unsigned int b8 = (b5 * 255) / 31;
39 printf("Scaled: R:%u G:%u B:%u\n", r8, g8, b8);
40
41 // Example: modifying a bit field
42 unsigned int reg = 0x12345678;
43 reg = set_bits(reg, 12, 8, 0xAB); // Replace bits 12-19
44 printf("\nModified register: 0x%08X\n", reg);
45
46 return 0;
47}
Bit Fields in Structs

C structs can contain bit fields: members that occupy a specified number of bits. This is useful for memory-efficient data structures and hardware register mapping. The layout of bit fields is implementation-defined.

bit_fields.c
C
1#include <stdio.h>
2#include <stdint.h>
3
4// Network packet header (simplified)
5typedef struct {
6 uint32_t version : 4; // 4 bits
7 uint32_t ihl : 4; // 4 bits
8 uint32_t dscp : 6; // 6 bits
9 uint32_t ecn : 2; // 2 bits
10 uint32_t length : 16; // 16 bits
11} IPv4Header;
12
13// RGB color as bit field
14typedef struct {
15 uint16_t blue : 5;
16 uint16_t green : 6;
17 uint16_t red : 5;
18} RGB565;
19
20// Hardware register layout
21typedef struct {
22 uint32_t enable : 1;
23 uint32_t mode : 3;
24 uint32_t prescaler : 4;
25 uint32_t reserved : 24;
26} TimerRegister;
27
28// Status flags packed into one byte
29typedef struct {
30 uint8_t running : 1;
31 uint8_t error : 1;
32 uint8_t warning : 1;
33 uint8_t mode : 2;
34 uint8_t priority : 3;
35} StatusFlags;
36
37int main(void) {
38 // IPv4 header
39 IPv4Header pkt = {
40 .version = 4,
41 .ihl = 5,
42 .dscp = 0,
43 .ecn = 0,
44 .length = 60
45 };
46 printf("IPv4 v=%u ihl=%u len=%u\n", pkt.version, pkt.ihl, pkt.length);
47 printf("Struct size: %zu bytes\n", sizeof(pkt));
48
49 // RGB565 color
50 RGB565 color = { .blue = 0, .green = 63, .red = 31 };
51 printf("RGB565 size: %zu bytes\n", sizeof(color));
52
53 // Status flags
54 StatusFlags st = { .running = 1, .error = 0, .mode = 3, .priority = 5 };
55 printf("Status: running=%u error=%u mode=%u pri=%u\n",
56 st.running, st.error, st.mode, st.priority);
57
58 return 0;
59}
📝

note

Bit fields are great for memory efficiency and hardware register mapping, but their layout is implementation-defined. Do not use bit fields for data that crosses compilation boundaries (network protocols, file formats) without careful attention to endianness and padding.
Practical: Permission Flags (R/W/X)

Unix-style file permissions are the canonical use case for bitwise operations. Each permission is a single bit, and operations combine, check, and modify them efficiently.

permission_flags.c
C
1#include <stdio.h>
2
3// Unix-style permission bits
4#define PERM_R_USER (1 << 0) // 000000001
5#define PERM_W_USER (1 << 1) // 000000010
6#define PERM_X_USER (1 << 2) // 000000100
7#define PERM_R_GROUP (1 << 3) // 000001000
8#define PERM_W_GROUP (1 << 4) // 000010000
9#define PERM_X_GROUP (1 << 5) // 000100000
10#define PERM_R_OTHER (1 << 6) // 001000000
11#define PERM_W_OTHER (1 << 7) // 010000000
12#define PERM_X_OTHER (1 << 8) // 100000000
13
14#define PERM_ALL_READ (PERM_R_USER | PERM_R_GROUP | PERM_R_OTHER)
15#define PERM_ALL_WRITE (PERM_W_USER | PERM_W_GROUP | PERM_W_OTHER)
16#define PERM_ALL_EXEC (PERM_X_USER | PERM_X_GROUP | PERM_X_OTHER)
17
18#define PERM_RWX_USER (PERM_R_USER | PERM_W_USER | PERM_X_USER)
19#define PERM_RWX_ALL (PERM_ALL_READ | PERM_ALL_WRITE | PERM_ALL_EXEC)
20
21void print_permissions(int perm) {
22 printf("%c%c%c%c%c%c%c%c%c\n",
23 (perm & PERM_R_USER) ? 'r' : '-',
24 (perm & PERM_W_USER) ? 'w' : '-',
25 (perm & PERM_X_USER) ? 'x' : '-',
26 (perm & PERM_R_GROUP) ? 'r' : '-',
27 (perm & PERM_W_GROUP) ? 'w' : '-',
28 (perm & PERM_X_GROUP) ? 'x' : '-',
29 (perm & PERM_R_OTHER) ? 'r' : '-',
30 (perm & PERM_W_OTHER) ? 'w' : '-',
31 (perm & PERM_X_OTHER) ? 'x' : '-');
32}
33
34int main(void) {
35 int perm = 0;
36
37 // Grant user read+write+execute
38 perm |= PERM_RWX_USER;
39
40 // Grant group read only
41 perm |= PERM_R_GROUP;
42
43 // Grant others nothing
44 // (already 0)
45
46 printf("Default: "); print_permissions(perm);
47
48 // Remove write permission from user
49 perm &= ~PERM_W_USER;
50 printf("No write: "); print_permissions(perm);
51
52 // Toggle execute for group
53 perm ^= PERM_X_GROUP;
54 printf("Toggle X: "); print_permissions(perm);
55
56 // Check if user has write permission
57 if (perm & PERM_W_USER) {
58 printf("User has write access\n");
59 } else {
60 printf("User has NO write access\n");
61 }
62
63 // Set full permissions
64 perm = PERM_RWX_ALL;
65 printf("Full: "); print_permissions(perm);
66
67 return 0;
68}
Practical: RGB Color Manipulation

Colors in graphics programming are often packed into a single 32-bit integer (0xAARRGGBB or 0xRRGGBBAA). Bitwise operations extract and combine channels efficiently.

color_manipulation.c
C
1#include <stdio.h>
2
3typedef unsigned int Color;
4
5// Extract channels from 0xAARRGGBB format
6#define COLOR_RED(c) (((c) >> 16) & 0xFF)
7#define COLOR_GREEN(c) (((c) >> 8) & 0xFF)
8#define COLOR_BLUE(c) ((c) & 0xFF)
9#define COLOR_ALPHA(c) (((c) >> 24) & 0xFF)
10
11// Create color from channels
12#define COLOR_MAKE(r, g, b, a) \
13 (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
14
15// Blend two colors (50% mix)
16Color color_blend(Color a, Color b) {
17 unsigned int ra = COLOR_RED(a), rb = COLOR_RED(b);
18 unsigned int ga = COLOR_GREEN(a), gb = COLOR_GREEN(b);
19 unsigned int ba = COLOR_BLUE(a), bb = COLOR_BLUE(b);
20 return COLOR_MAKE((ra + rb) / 2, (ga + gb) / 2, (ba + bb) / 2, 255);
21}
22
23// Invert color
24Color color_invert(Color c) {
25 return COLOR_MAKE(255 - COLOR_RED(c),
26 255 - COLOR_GREEN(c),
27 255 - COLOR_BLUE(c),
28 COLOR_ALPHA(c));
29}
30
31// Grayscale conversion (weighted average)
32Color color_grayscale(Color c) {
33 unsigned int gray = (COLOR_RED(c) * 77 + COLOR_GREEN(c) * 150 +
34 COLOR_BLUE(c) * 29) >> 8;
35 return COLOR_MAKE(gray, gray, gray, COLOR_ALPHA(c));
36}
37
38int main(void) {
39 Color sky = COLOR_MAKE(135, 206, 235, 255); // Sky blue
40 Color orange = COLOR_MAKE(255, 165, 0, 255); // Orange
41
42 printf("Sky: R=%u G=%u B=%u A=%u\n",
43 COLOR_RED(sky), COLOR_GREEN(sky), COLOR_BLUE(sky), COLOR_ALPHA(sky));
44
45 Color blend = color_blend(sky, orange);
46 printf("Blend: R=%u G=%u B=%u\n",
47 COLOR_RED(blend), COLOR_GREEN(blend), COLOR_BLUE(blend));
48
49 Color inv = color_invert(sky);
50 printf("Invert: R=%u G=%u B=%u\n",
51 COLOR_RED(inv), COLOR_GREEN(inv), COLOR_BLUE(inv));
52
53 Color gray = color_grayscale(sky);
54 printf("Gray: R=%u G=%u B=%u\n",
55 COLOR_RED(gray), COLOR_GREEN(gray), COLOR_BLUE(gray));
56
57 return 0;
58}
Bit Counting Algorithms

Counting the number of set bits (population count or popcount) is a fundamental operation in computer science, used in hashing, compression, and error detection.

bit_counting.c
C
1#include <stdio.h>
2
3// Naive approach: check each bit
4int count_bits_naive(unsigned int n) {
5 int count = 0;
6 while (n) {
7 count += n & 1;
8 n >>= 1;
9 }
10 return count;
11}
12
13// Brian Kernighan's algorithm: n & (n-1) clears lowest set bit
14int count_bits_kernighan(unsigned int n) {
15 int count = 0;
16 while (n) {
17 n &= (n - 1); // Clear lowest set bit
18 count++;
19 }
20 return count;
21}
22
23// Lookup table (8-bit at a time)
24static unsigned char popcount_table[256];
25
26void init_popcount_table(void) {
27 for (int i = 0; i < 256; i++) {
28 popcount_table[i] = popcount_table[i >> 1] + (i & 1);
29 }
30}
31
32int count_bits_table(unsigned int n) {
33 return popcount_table[n & 0xFF] +
34 popcount_table[(n >> 8) & 0xFF] +
35 popcount_table[(n >> 16) & 0xFF] +
36 popcount_table[(n >> 24) & 0xFF];
37}
38
39// GCC builtin (most efficient on supported compilers)
40// int count = __builtin_popcount(n);
41
42int main(void) {
43 init_popcount_table();
44
45 unsigned int values[] = {0, 1, 0xFF, 0x12345678, 0xFFFFFFFF};
46 int num_values = sizeof(values) / sizeof(values[0]);
47
48 for (int i = 0; i < num_values; i++) {
49 unsigned int v = values[i];
50 printf("0x%08X: naive=%d, kernighan=%d, table=%d\n",
51 v,
52 count_bits_naive(v),
53 count_bits_kernighan(v),
54 count_bits_table(v));
55 }
56
57 return 0;
58}
Reverse Bits of an Integer

Reversing the bits of an integer is a common operation in cryptography, hash functions, and data serialization. Here are two approaches: byte-by-byte reversal using a lookup table, and the full bit reversal algorithm.

reverse_bits.c
C
1#include <stdio.h>
2
3// Reverse all bits of a 32-bit integer
4unsigned int reverse_bits(unsigned int n) {
5 unsigned int result = 0;
6 for (int i = 0; i < 32; i++) {
7 result = (result << 1) | (n & 1);
8 n >>= 1;
9 }
10 return result;
11}
12
13// Reverse bits using lookup table (byte at a time)
14static unsigned char reverse_table[256];
15
16void init_reverse_table(void) {
17 for (int i = 0; i < 256; i++) {
18 unsigned char r = 0;
19 unsigned char v = (unsigned char)i;
20 for (int j = 0; j < 8; j++) {
21 r = (r << 1) | (v & 1);
22 v >>= 1;
23 }
24 reverse_table[i] = r;
25 }
26}
27
28unsigned int reverse_bits_lut(unsigned int n) {
29 return ((unsigned int)reverse_table[n & 0xFF] << 24) |
30 ((unsigned int)reverse_table[(n >> 8) & 0xFF] << 16) |
31 ((unsigned int)reverse_table[(n >> 16) & 0xFF] << 8) |
32 ((unsigned int)reverse_table[(n >> 24) & 0xFF]);
33}
34
35// Reverse bits of a byte
36unsigned char reverse_byte(unsigned char b) {
37 b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
38 b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
39 b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
40 return b;
41}
42
43void print_bin(unsigned int n, int bits) {
44 for (int i = bits - 1; i >= 0; i--)
45 putchar((n >> i) & 1 ? '1' : '0');
46}
47
48int main(void) {
49 init_reverse_table();
50
51 unsigned int val = 0x12345678;
52 unsigned int rev = reverse_bits(val);
53
54 printf("Original: 0x%08X = ", val); print_bin(val, 32); printf("\n");
55 printf("Reversed: 0x%08X = ", rev); print_bin(rev, 32); printf("\n");
56
57 unsigned char byte = 0b11010010;
58 printf("\nByte 0x%02X reversed: 0x%02X\n", byte, reverse_byte(byte));
59
60 return 0;
61}
Check if a Number is a Power of 2

A classic bit manipulation trick: a power of 2 in binary has exactly one set bit. Subtracting 1 from it flips all bits below that bit. AND-ing them gives 0 if and only if the number is a power of 2.

power_of_2.c
C
1#include <stdio.h>
2#include <stdbool.h>
3
4// Classic check: n is power of 2 if n & (n-1) == 0
5// Also requires n > 0 to exclude 0
6bool is_power_of_2(unsigned int n) {
7 return n > 0 && (n & (n - 1)) == 0;
8}
9
10// Find next power of 2 (round up)
11unsigned int next_power_of_2(unsigned int n) {
12 if (n == 0) return 1;
13 n--;
14 n |= n >> 1;
15 n |= n >> 2;
16 n |= n >> 4;
17 n |= n >> 8;
18 n |= n >> 16;
19 return n + 1;
20}
21
22// Find previous power of 2 (round down)
23unsigned int prev_power_of_2(unsigned int n) {
24 if (n == 0) return 0;
25 n |= n >> 1;
26 n |= n >> 2;
27 n |= n >> 4;
28 n |= n >> 8;
29 n |= n >> 16;
30 return (n + 1) >> 1;
31}
32
33int main(void) {
34 // Test power of 2 check
35 unsigned int test_values[] = {0, 1, 2, 3, 4, 15, 16, 17, 64, 100, 1024};
36 int count = sizeof(test_values) / sizeof(test_values[0]);
37
38 for (int i = 0; i < count; i++) {
39 printf("%5u is %sa power of 2\n",
40 test_values[i],
41 is_power_of_2(test_values[i]) ? "" : "NOT ");
42 }
43
44 printf("\nNext power of 2:\n");
45 unsigned int vals[] = {0, 1, 3, 5, 7, 15, 16, 100, 1000};
46 for (int i = 0; i < 9; i++) {
47 printf(" next_pow2(%u) = %u\n", vals[i], next_power_of_2(vals[i]));
48 }
49
50 return 0;
51}
🔥

pro tip

The n & (n-1) == 0 trick is one of the most famous bit manipulation patterns. It appears in interviews, competitive programming, and production code. Memorize it.
Bit Manipulation in Embedded Systems

Embedded systems rely heavily on bitwise operations to configure hardware registers. Each bit in a control register typically enables, disables, or selects a specific hardware feature.

embedded_registers.c
C
1#include <stdio.h>
2#include <stdint.h>
3
4// Simulated hardware register addresses
5#define TIMER_CR (*(volatile uint32_t *)0x40001000) // Control register
6#define TIMER_SR (*(volatile uint32_t *)0x40001004) // Status register
7#define TIMER_CNT (*(volatile uint32_t *)0x40001008) // Counter
8#define TIMER_PSC (*(volatile uint32_t *)0x4000100C) // Prescaler
9#define TIMER_ARR (*(volatile uint32_t *)0x40001010) // Auto-reload
10
11// Control register bit definitions
12#define TIM_CR1_CEN (1u << 0) // Counter enable
13#define TIM_CR1_UDIS (1u << 1) // Update disable
14#define TIM_CR1_URS (1u << 2) // Update request source
15#define TIM_CR1_OPM (1u << 3) // One-pulse mode
16#define TIM_CR1_DIR (1u << 4) // Direction
17#define TIM_CR1_CMS_0 (1u << 5) // Center-aligned mode bit 0
18#define TIM_CR1_CMS_1 (1u << 6) // Center-aligned mode bit 1
19#define TIM_CR1_ARPE (1u << 7) // Auto-reload preload enable
20
21// Status register bits
22#define TIM_SR_UIF (1u << 0) // Update interrupt flag
23#define TIM_SR_CC1IF (1u << 1) // Capture/compare 1 flag
24
25// Set bits in a register
26static inline void reg_set(volatile uint32_t *reg, uint32_t mask) {
27 *reg |= mask;
28}
29
30// Clear bits in a register
31static inline void reg_clear(volatile uint32_t *reg, uint32_t mask) {
32 *reg &= ~mask;
33}
34
35// Read-modify-write with field replacement
36static inline uint32_t reg_replace(volatile uint32_t *reg,
37 uint32_t mask, uint32_t value) {
38 uint32_t old = *reg;
39 *reg = (old & ~mask) | (value & mask);
40 return old;
41}
42
43int main(void) {
44 // Simulate register manipulation
45 uint32_t timer_cr = 0;
46
47 // Enable timer with one-pulse mode
48 timer_cr |= TIM_CR1_CEN | TIM_CR1_OPM;
49
50 // Set prescaler to 8 (bits 0-3 of prescaler register)
51 uint32_t psc = 8;
52 timer_cr = (timer_cr & ~(0xFu)) | (psc & 0xF);
53
54 printf("Timer CR: 0x%08X\n", timer_cr);
55 printf(" CEN=%d OPM=%d\n",
56 (timer_cr & TIM_CR1_CEN) != 0,
57 (timer_cr & TIM_CR1_OPM) != 0);
58
59 // Check and clear interrupt flag
60 uint32_t status = TIM_SR_UIF | TIM_SR_CC1IF;
61 if (status & TIM_SR_UIF) {
62 printf("Update interrupt pending\n");
63 status &= ~TIM_SR_UIF; // Clear flag
64 }
65
66 return 0;
67}
Endianness: Big-endian vs Little-endian

Endianness determines the byte order of multi-byte values in memory. Big-endian stores the most significant byte first (network byte order). Little-endian stores the least significant byte first (x86, ARM).

endianness.c
C
1#include <stdio.h>
2#include <stdint.h>
3
4// Detect endianness at compile time
5#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
6 #define IS_LITTLE_ENDIAN 1
7#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
8 #define IS_LITTLE_ENDIAN 0
9#else
10 #define IS_LITTLE_ENDIAN (-1) // Unknown
11#endif
12
13// Byte swap macros
14#define BSWAP16(x) \
15 (((uint16_t)(x) >> 8) | ((uint16_t)(x) << 8))
16
17#define BSWAP32(x) \
18 (((uint32_t)(x) >> 24) | \
19 (((uint32_t)(x) >> 8) & 0x0000FF00) | \
20 (((uint32_t)(x) << 8) & 0x00FF0000) | \
21 ((uint32_t)(x) << 24))
22
23// Host to network byte order (big-endian)
24uint32_t htonl(uint32_t hostlong) {
25 #if IS_LITTLE_ENDIAN
26 return BSWAP32(hostlong);
27 #else
28 return hostlong;
29 #endif
30}
31
32uint16_t htons(uint16_t hostshort) {
33 #if IS_LITTLE_ENDIAN
34 return BSWAP16(hostshort);
35 #else
36 return hostshort;
37 #endif
38}
39
40// Show memory layout of a multi-byte value
41void show_memory(const void *ptr, size_t size) {
42 const unsigned char *p = (const unsigned char *)ptr;
43 printf("Address: 0x%016lX Value: ", (unsigned long)(uintptr_t)p);
44 for (size_t i = 0; i < size; i++) {
45 printf("%02X ", p[i]);
46 }
47 printf("\n");
48}
49
50int main(void) {
51 printf("Endianness: %s\n",
52 IS_LITTLE_ENDIAN ? "Little-endian" : "Big-endian");
53
54 uint32_t val = 0x12345678;
55 uint16_t sval = 0xABCD;
56
57 printf("\nuint32_t 0x%08X:\n", val);
58 show_memory(&val, sizeof(val));
59
60 printf("uint16_t 0x%04X:\n", sval);
61 show_memory(&sval, sizeof(sval));
62
63 printf("\nhtonl(0x12345678) = 0x%08X\n", htonl(0x12345678));
64 printf("htons(0xABCD) = 0x%04X\n", htons(0xABCD));
65
66 // Byte swap and restore
67 uint32_t original = 0xDEADBEEF;
68 uint32_t swapped = BSWAP32(original);
69 uint32_t restored = BSWAP32(swapped);
70 printf("\nOriginal: 0x%08X\n", original);
71 printf("Swapped: 0x%08X\n", swapped);
72 printf("Restored: 0x%08X\n", restored);
73
74 return 0;
75}

warning

Endianness matters when serializing data to files or sending it over networks. Always convert multi-byte values to a known byte order (typically big-endian/network order) before transmission, and convert back on receipt.
Advanced Bit Tricks

These are elegant bit manipulation techniques that appear in interviews, competitive programming, and performance-critical code. Understanding them deepens your grasp of binary arithmetic.

bit_tricks.c
C
1#include <stdio.h>
2
3// Find the lowest set bit
4int lowest_set_bit(unsigned int n) {
5 return n & (-n);
6}
7
8// Check if a number is a power of 2 (already covered, but compact)
9int is_pow2(unsigned int n) { return n && !(n & (n - 1)); }
10
11// Count trailing zeros (CTZ) — number of 0 bits before first 1
12int count_trailing_zeros(unsigned int n) {
13 if (n == 0) return 32;
14 int count = 0;
15 while ((n & 1) == 0) {
16 n >>= 1;
17 count++;
18 }
19 return count;
20}
21
22// Count leading zeros (CLZ) — number of 0 bits after the MSB
23int count_leading_zeros(unsigned int n) {
24 if (n == 0) return 32;
25 int count = 0;
26 unsigned int mask = 1u << 31;
27 while ((n & mask) == 0) {
28 count++;
29 mask >>= 1;
30 }
31 return count;
32}
33
34// Round up to next multiple of alignment (power of 2)
35unsigned int align_up(unsigned int value, unsigned int align) {
36 return (value + align - 1) & ~(align - 1);
37}
38
39// Round down to previous multiple of alignment
40unsigned int align_down(unsigned int value, unsigned int align) {
41 return value & ~(align - 1);
42}
43
44// Absolute value without branch (works for 2's complement)
45int abs_branchless(int x) {
46 int mask = x >> 31; // All 1s if negative, all 0s if positive
47 return (x + mask) ^ mask;
48}
49
50// Max/min without branches
51int max_branchless(int a, int b) {
52 int diff = a - b;
53 int sign = diff >> 31;
54 return b + (diff & ~sign);
55}
56
57int main(void) {
58 unsigned int n = 0b10110100;
59
60 printf("n = 0x%02X\n", n);
61 printf("lowest bit = 0x%02X\n", lowest_set_bit(n));
62 printf("trailing 0s = %d\n", count_trailing_zeros(n));
63 printf("leading 0s = %d\n", count_leading_zeros(n));
64 printf("is power of 2: %d\n", is_pow2(n));
65
66 printf("\nAlignment:\n");
67 printf(" align_up(17, 8) = %u\n", align_up(17, 8)); // 24
68 printf(" align_down(17, 8) = %u\n", align_down(17, 8)); // 16
69 printf(" align_up(16, 8) = %u\n", align_up(16, 8)); // 16
70
71 printf("\nBranchless:\n");
72 printf(" abs(-5) = %d\n", abs_branchless(-5));
73 printf(" max(3, 7) = %d\n", max_branchless(3, 7));
74 printf(" max(7, 3) = %d\n", max_branchless(7, 3));
75
76 return 0;
77}
Practical: Bit-Packed Data Structures

Bit packing stores multiple small values in fewer bytes. This is used in network protocols, file formats, compression, and embedded systems where memory is limited.

bit_packing.c
C
1#include <stdio.h>
2#include <stdint.h>
3#include <string.h>
4
5// Example: Packed date format
6// Bits [31:25] = year (0-99, 7 bits)
7// Bits [24:21] = month (1-12, 4 bits)
8// Bits [20:16] = day (1-31, 5 bits)
9// Bits [15:11] = hour (0-23, 5 bits)
10// Bits [10:5] = minute (0-59, 6 bits)
11// Bits [4:0] = second (0-59, 5 bits)
12
13typedef uint32_t PackedDate;
14
15PackedDate pack_date(int year, int month, int day,
16 int hour, int minute, int second) {
17 return ((uint32_t)(year & 0x7F) << 25) |
18 ((uint32_t)(month & 0x0F) << 21) |
19 ((uint32_t)(day & 0x1F) << 16) |
20 ((uint32_t)(hour & 0x1F) << 11) |
21 ((uint32_t)(minute & 0x3F) << 5) |
22 ((uint32_t)(second & 0x1F));
23}
24
25void unpack_date(PackedDate d, int *year, int *month, int *day,
26 int *hour, int *minute, int *second) {
27 *year = (d >> 25) & 0x7F;
28 *month = (d >> 21) & 0x0F;
29 *day = (d >> 16) & 0x1F;
30 *hour = (d >> 11) & 0x1F;
31 *minute = (d >> 5) & 0x3F;
32 *second = d & 0x1F;
33}
34
35// Example: Bit-packed boolean array
36#define BITARRAY_SIZE(n) (((n) + 7) / 8)
37
38void bitarray_set(uint8_t *arr, int index) {
39 arr[index / 8] |= (1u << (index % 8));
40}
41
42void bitarray_clear(uint8_t *arr, int index) {
43 arr[index / 8] &= ~(1u << (index % 8));
44}
45
46int bitarray_get(uint8_t *arr, int index) {
47 return (arr[index / 8] >> (index % 8)) & 1;
48}
49
50int main(void) {
51 // Packed date demo
52 PackedDate d = pack_date(26, 7, 13, 14, 30, 0);
53 printf("Packed size: %zu bytes\n", sizeof(d));
54
55 int y, m, day, h, min, s;
56 unpack_date(d, &y, &m, &day, &h, &min, &s);
57 printf("Unpacked: %02d-%02d-%02d %02d:%02d:%02d\n",
58 y, m, day, h, min, s);
59
60 // Bit-packed boolean array: 1000 booleans in 125 bytes
61 uint8_t flags[BITARRAY_SIZE(1000)];
62 memset(flags, 0, sizeof(flags));
63
64 // Set some bits
65 bitarray_set(flags, 0);
66 bitarray_set(flags, 42);
67 bitarray_set(flags, 999);
68
69 printf("\nBitarray: 0=%d, 42=%d, 999=%d, 1=%d\n",
70 bitarray_get(flags, 0),
71 bitarray_get(flags, 42),
72 bitarray_get(flags, 999),
73 bitarray_get(flags, 1));
74
75 return 0;
76}
$Blueprint — Engineering Documentation·Section ID: C-BITWISE·Revision: 1.0