Enums and Bitwise Operations
C enums, bitwise operators, and the flags pattern
Introduction
In this lesson, you'll learn about enums and bitwise operations in C. Coming from JavaScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In JavaScript, you're familiar with c enums, bitwise operators, and the flags pattern.
C has its own approach to c enums, bitwise operators, and the flags pattern, which we'll explore step by step.
The C Way
Let's see how C handles this concept. Here's a typical example:
#include <stdio.h>
// enum — integer constants starting at 0
typedef enum {
NORTH = 0,
SOUTH, // 1
EAST, // 2
WEST // 3
} Direction;
// Enum with explicit values (flags/bitmask pattern)
typedef enum {
PERM_NONE = 0,
PERM_READ = 1 << 0, // 1
PERM_WRITE = 1 << 1, // 2
PERM_EXECUTE = 1 << 2, // 4
PERM_RW = PERM_READ | PERM_WRITE // 3
} Permission;
int main(void) {
Direction dir = NORTH;
// Bitwise ops
Permission perm = PERM_READ | PERM_WRITE; // 3
// Test a flag
if (perm & PERM_READ) printf("can read\n");
// Add a flag
perm |= PERM_EXECUTE;
// Remove a flag
perm &= ~PERM_WRITE;
// Toggle a flag
perm ^= PERM_EXECUTE;
// Shift
int x = 1 << 4; // 16 — power of 2
int y = 64 >> 2; // 16
printf("perm=%d\n", perm);
return 0;
}Comparing to JavaScript
Here's how you might have written similar code in JavaScript:
// Enum-like constants via Object.freeze
const Direction = Object.freeze({
NORTH: 0, SOUTH: 1, EAST: 2, WEST: 3
});
// Flags via bit values
const Perm = Object.freeze({
READ: 0b001, // 1
WRITE: 0b010, // 2
EXECUTE: 0b100, // 4
});
// Combine flags
let perm = Perm.READ | Perm.WRITE; // 3
// Test flag
if (perm & Perm.READ) console.log("can read");
// Remove flag
perm &= ~Perm.WRITE; // 1 (remove write)
// Shift operators
1 << 3; // 8 (left shift)
16 >> 2; // 4 (right shift)You may be used to different syntax or behavior.
C enum assigns consecutive integers starting at 0; or explicit values
You may be used to different syntax or behavior.
enum values are just integers — no runtime type checking
You may be used to different syntax or behavior.
typedef enum {} Name; makes Name usable without 'enum' prefix
You may be used to different syntax or behavior.
Bitwise operators: & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift)
You may be used to different syntax or behavior.
Flags pattern: use 1 << n for each bit, combine with |, test with &, clear with &= ~flag
Step-by-Step Breakdown
1. Defining Enums
enum creates a named integer type with symbolic constants. typedef makes the name usable without the 'enum' keyword prefix.
const Color = Object.freeze({ RED: 0, GREEN: 1, BLUE: 2 });typedef enum { RED, GREEN, BLUE } Color;
Color c = GREEN; // c == 12. Flags Pattern
Use 1 << n for each bit flag so each flag occupies a unique bit. Combine flags with OR, test with AND.
const flags = Perm.READ | Perm.WRITE;
if (flags & Perm.READ) { ... }typedef enum {
FLAG_A = 1 << 0, // 001
FLAG_B = 1 << 1, // 010
FLAG_C = 1 << 2 // 100
} Flags;
Flags f = FLAG_A | FLAG_C; // 101
if (f & FLAG_A) { /* set */ }3. Add, Remove, Toggle Flags
Three essential bit operations for flag manipulation: |= to add, &= ~flag to remove, ^= to toggle.
perm |= Perm.WRITE; // add
perm &= ~Perm.WRITE; // remove
perm ^= Perm.WRITE; // toggleperm |= PERM_WRITE; // set bit
perm &= ~PERM_WRITE; // clear bit (~inverts all bits)
perm ^= PERM_WRITE; // toggle bit4. Shift Operators
Left shift (<<) multiplies by 2^n; right shift (>>) divides by 2^n. Both are O(1) and faster than multiplication for powers of 2.
1 << 3 // 8
256 >> 4 // 16int page_size = 1 << 12; // 4096
int half = value >> 1; // divide by 2
int byte_idx = bit_pos >> 3; // which byte contains this bitCommon Mistakes
When coming from JavaScript, developers often make these mistakes:
- C enum assigns consecutive integers starting at 0; or explicit values
- enum values are just integers — no runtime type checking
- typedef enum {} Name; makes Name usable without 'enum' prefix
Key Takeaways
- typedef enum { A, B, C } Name; — A=0, B=1, C=2 by default
- Flags: use 1 << n for each bit; combine with |, test with &, clear with &= ~flag
- Shift operators: << multiplies by power of 2, >> divides
- Use unsigned types (unsigned int) for bit manipulation — signed right-shift is implementation-defined