C — Bitwise Operations
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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Print binary representation of an integer |
| 4 | void 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 | |
| 11 | int 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 | } |
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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | void print_binary_8(unsigned char n) { |
| 4 | for (int i = 7; i >= 0; i--) { |
| 5 | putchar((n >> i) & 1 ? '1' : '0'); |
| 6 | } |
| 7 | } |
| 8 | |
| 9 | int 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 | } |
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.
| 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 | |
| 9 | const 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 | |
| 19 | void 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 | |
| 29 | int 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 | } |
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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int 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 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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int 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 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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int 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 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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int 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
Bit manipulation fundamentals: setting, clearing, toggling, and checking individual bits. These operations form the foundation of embedded systems, networking, and file permission code.
| 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 | |
| 24 | void 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 | |
| 29 | int 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 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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Extract bits [start, start+width) from value |
| 4 | unsigned 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 |
| 10 | unsigned 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 | |
| 16 | int 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 | } |
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.
| 1 | #include <stdio.h> |
| 2 | #include <stdint.h> |
| 3 | |
| 4 | // Network packet header (simplified) |
| 5 | typedef 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 |
| 14 | typedef struct { |
| 15 | uint16_t blue : 5; |
| 16 | uint16_t green : 6; |
| 17 | uint16_t red : 5; |
| 18 | } RGB565; |
| 19 | |
| 20 | // Hardware register layout |
| 21 | typedef 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 |
| 29 | typedef 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 | |
| 37 | int 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
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.
| 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 | |
| 21 | void 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 | |
| 34 | int 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 | } |
Colors in graphics programming are often packed into a single 32-bit integer (0xAARRGGBB or 0xRRGGBBAA). Bitwise operations extract and combine channels efficiently.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | typedef 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) |
| 16 | Color 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 |
| 24 | Color 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) |
| 32 | Color 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 | |
| 38 | int 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 | } |
Counting the number of set bits (population count or popcount) is a fundamental operation in computer science, used in hashing, compression, and error detection.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Naive approach: check each bit |
| 4 | int 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 |
| 14 | int 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) |
| 24 | static unsigned char popcount_table[256]; |
| 25 | |
| 26 | void 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 | |
| 32 | int 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 | |
| 42 | int 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 | } |
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.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Reverse all bits of a 32-bit integer |
| 4 | unsigned 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) |
| 14 | static unsigned char reverse_table[256]; |
| 15 | |
| 16 | void 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 | |
| 28 | unsigned 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 |
| 36 | unsigned 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 | |
| 43 | void 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 | |
| 48 | int 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 | } |
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.
| 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 |
| 6 | bool 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) |
| 11 | unsigned 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) |
| 23 | unsigned 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 | |
| 33 | int 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
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.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.
| 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 |
| 26 | static inline void reg_set(volatile uint32_t *reg, uint32_t mask) { |
| 27 | *reg |= mask; |
| 28 | } |
| 29 | |
| 30 | // Clear bits in a register |
| 31 | static inline void reg_clear(volatile uint32_t *reg, uint32_t mask) { |
| 32 | *reg &= ~mask; |
| 33 | } |
| 34 | |
| 35 | // Read-modify-write with field replacement |
| 36 | static 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 | |
| 43 | int 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 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).
| 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) |
| 24 | uint32_t htonl(uint32_t hostlong) { |
| 25 | #if IS_LITTLE_ENDIAN |
| 26 | return BSWAP32(hostlong); |
| 27 | #else |
| 28 | return hostlong; |
| 29 | #endif |
| 30 | } |
| 31 | |
| 32 | uint16_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 |
| 41 | void 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 | |
| 50 | int 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
These are elegant bit manipulation techniques that appear in interviews, competitive programming, and performance-critical code. Understanding them deepens your grasp of binary arithmetic.
| 1 | #include <stdio.h> |
| 2 | |
| 3 | // Find the lowest set bit |
| 4 | int 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) |
| 9 | int is_pow2(unsigned int n) { return n && !(n & (n - 1)); } |
| 10 | |
| 11 | // Count trailing zeros (CTZ) — number of 0 bits before first 1 |
| 12 | int 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 |
| 23 | int 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) |
| 35 | unsigned 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 |
| 40 | unsigned 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) |
| 45 | int 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 |
| 51 | int max_branchless(int a, int b) { |
| 52 | int diff = a - b; |
| 53 | int sign = diff >> 31; |
| 54 | return b + (diff & ~sign); |
| 55 | } |
| 56 | |
| 57 | int 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 | } |
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.
| 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 | |
| 13 | typedef uint32_t PackedDate; |
| 14 | |
| 15 | PackedDate 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 | |
| 25 | void 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 | |
| 38 | void bitarray_set(uint8_t *arr, int index) { |
| 39 | arr[index / 8] |= (1u << (index % 8)); |
| 40 | } |
| 41 | |
| 42 | void bitarray_clear(uint8_t *arr, int index) { |
| 43 | arr[index / 8] &= ~(1u << (index % 8)); |
| 44 | } |
| 45 | |
| 46 | int bitarray_get(uint8_t *arr, int index) { |
| 47 | return (arr[index / 8] >> (index % 8)) & 1; |
| 48 | } |
| 49 | |
| 50 | int 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 | } |