Home C / C++ Bitwise Operators in C Explained — How, Why, and When to Use Them

Bitwise Operators in C Explained — How, Why, and When to Use Them

In Plain English 🔥
Imagine a row of 8 light switches on a wall — each one is either ON (1) or OFF (0). Bitwise operators are like instructions for flipping, checking, or combining those switches in bulk, all at once, in a single CPU instruction. Instead of 'is the number greater than 5?', you're asking 'is this specific switch flipped on?' That's it. Numbers are just patterns of switches, and bitwise operators let you manipulate those patterns with surgical precision.
⚡ Quick Answer
Imagine a row of 8 light switches on a wall — each one is either ON (1) or OFF (0). Bitwise operators are like instructions for flipping, checking, or combining those switches in bulk, all at once, in a single CPU instruction. Instead of 'is the number greater than 5?', you're asking 'is this specific switch flipped on?' That's it. Numbers are just patterns of switches, and bitwise operators let you manipulate those patterns with surgical precision.

Embedded systems, operating system kernels, game engines, network protocols, and cryptography libraries all share one thing in common — they rely heavily on bitwise operators. When a Linux kernel sets a file permission flag, it flips a bit. When a graphics engine packs RGBA color channels into a single 32-bit integer, it uses bit shifts. When a network driver extracts the IP header length from a raw packet, it masks bits. This isn't niche knowledge — it's the lingua franca of systems programming.

The problem bitwise operators solve is deceptively simple: speed and space. Storing 8 boolean flags as 8 separate int variables wastes memory and slows things down. Packing them into a single byte and using bitwise operators to read and write individual flags is both faster and leaner. The CPU handles bitwise operations in a single clock cycle — there's nothing faster in your toolbox.

By the end of this article you'll be able to read and write bit flags confidently, understand exactly how masking and shifting work under the hood, spot the classic bugs that trip up even experienced developers, and answer the bitwise questions that interviewers love to spring on candidates. Let's get into it.

The Six Bitwise Operators and What They Actually Do

C gives you six bitwise operators: AND (&), OR (|), XOR (^), NOT (~), left shift (<<), and right shift (>>). Each one operates on the individual bits of an integer — not the integer as a whole mathematical value, but the raw binary pattern sitting in memory.

Think of AND as 'both switches must be ON'. OR as 'at least one switch must be ON'. XOR (exclusive OR) as 'exactly one switch must be ON — but not both'. NOT flips every single switch. Left shift moves all switches to the left by N positions, filling vacated positions with zeros — which multiplies by powers of two. Right shift moves everything right, which divides by powers of two.

The key insight beginners miss: these operators don't care about the numeric value you intended. They only see the bit pattern. The number 5 (binary: 00000101) and the number 6 (binary: 00000110) ANDed together produce 4 (binary: 00000100) — not because 5 AND 6 means anything mathematically, but because only the third bit was ON in both patterns.

This distinction between 'value' and 'bit pattern' is everything. Once it clicks, the rest falls into place naturally.

bitwise_basics.c · C
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
#include <stdio.h>

// Helper: print an integer as 8-bit binary so we can SEE what's happening
void print_binary(const char *label, unsigned int value) {
    printf("%-20s = ", label);
    for (int bit_position = 7; bit_position >= 0; bit_position--) {
        // Shift the bit we care about all the way to position 0, then mask it
        printf("%d", (value >> bit_position) & 1);
    }
    printf(" (decimal: %u)\n", value);
}

int main(void) {
    unsigned int lights_a = 0b00110101; // switches: 6,5 and 4,2 and 0 are ON  = 53
    unsigned int lights_b = 0b00101110; // switches: 5,3,2,1 are ON            = 46

    printf("=== Starting values ===\n");
    print_binary("lights_a",         lights_a);
    print_binary("lights_b",         lights_b);

    printf("\n=== AND — both must be ON ===\n");
    // Only bits that are 1 in BOTH patterns survive
    print_binary("a & b",            lights_a & lights_b);

    printf("\n=== OR — at least one ON ===\n");
    // Any bit that is 1 in EITHER pattern survives
    print_binary("a | b",            lights_a | lights_b);

    printf("\n=== XOR — exactly one ON ===\n");
    // Bits that differ between the two patterns become 1
    print_binary("a ^ b",            lights_a ^ lights_b);

    printf("\n=== NOT — flip everything ===\n");
    // Every 0 becomes 1 and every 1 becomes 0 (we mask to 8 bits for clarity)
    print_binary("~a (8-bit masked)", (~lights_a) & 0xFF);

    printf("\n=== LEFT SHIFT — multiply by 2 per shift ===\n");
    // Shifting left by 2 is the same as multiplying by 4
    print_binary("a << 2",           (lights_a << 2) & 0xFF);

    printf("\n=== RIGHT SHIFT — divide by 2 per shift ===\n");
    // Shifting right by 1 is the same as integer division by 2
    print_binary("a >> 1",           lights_a >> 1);

    return 0;
}
▶ Output
=== Starting values ===
lights_a = 00110101 (decimal: 53)
lights_b = 00101110 (decimal: 46)

=== AND — both must be ON ===
a & b = 00100100 (decimal: 36)

=== OR — at least one ON ===
a | b = 00111111 (decimal: 63)

=== XOR — exactly one ON ===
a ^ b = 00011011 (decimal: 27)

=== NOT — flip everything ===
~a (8-bit masked) = 11001010 (decimal: 202)

=== LEFT SHIFT — multiply by 2 per shift ===
a << 2 = 11010100 (decimal: 212)

=== RIGHT SHIFT — divide by 2 per shift ===
a >> 1 = 00011010 (decimal: 26)
⚠️
Pro Tip: Use print_binary() as Your DebuggerCopy the print_binary() helper from this example into any project where you're doing bit manipulation. Seeing the raw bit pattern when debugging saves hours. Most bit bugs are immediately obvious the moment you print the binary representation.

Bit Flags and Masking — The Real-World Pattern You'll Use Every Day

The single most common real-world use of bitwise operators is the bit flag pattern. Instead of storing eight separate boolean variables, you pack them all into one integer — each bit represents one flag. This is how file permissions work in Linux (rwxrwxrwx is nine bits), how TCP/IP headers store control flags (SYN, ACK, FIN), and how game engines store entity states.

Three operations drive the entire pattern:

Setting a bit (turning a flag ON): Use OR with a mask. The mask has a 1 only in the position you want to set. Everything else stays untouched because OR-ing with 0 changes nothing.

Clearing a bit (turning a flag OFF): Use AND with an inverted mask. The inverted mask has a 0 only in the target position, which forces that bit to 0. Everything else stays untouched because AND-ing with 1 changes nothing.

Checking a bit (reading a flag): Use AND with the mask. If the result is non-zero, the bit was set. If zero, it wasn't.

The mask itself is almost always defined using a left shift: 1 << N gives you a mask with exactly one 1 bit at position N. This pattern is so idiomatic in C that you'll see it in virtually every low-level codebase.

permission_flags.c · C
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
#include <stdio.h>

// Define each permission as a bit flag using left shifts
// This makes the intent crystal-clear and avoids magic numbers
#define PERMISSION_READ     (1 << 0)  // bit 0 = 00000001 = 1
#define PERMISSION_WRITE    (1 << 1)  // bit 1 = 00000010 = 2
#define PERMISSION_EXECUTE  (1 << 2)  // bit 2 = 00000100 = 4
#define PERMISSION_DELETE   (1 << 3)  // bit 3 = 00001000 = 8
#define PERMISSION_SHARE    (1 << 4)  // bit 4 = 00010000 = 16

// A single byte can hold all 5 permissions — no wasted memory
typedef unsigned char PermissionSet;

void print_permissions(PermissionSet perms) {
    printf("Permissions: [%s] [%s] [%s] [%s] [%s]\n",
        (perms & PERMISSION_READ)    ? "READ"    : "----",
        (perms & PERMISSION_WRITE)   ? "WRITE"   : "-----",
        (perms & PERMISSION_EXECUTE) ? "EXECUTE" : "-------",
        (perms & PERMISSION_DELETE)  ? "DELETE"  : "------",
        (perms & PERMISSION_SHARE)   ? "SHARE"   : "-----"
    );
}

int main(void) {
    // Start with no permissions at all — a clean slate
    PermissionSet user_permissions = 0;

    printf("--- Initial state ---\n");
    print_permissions(user_permissions);

    // SET bits: grant read and write using OR
    // OR with the flag mask forces that bit to 1 without touching others
    user_permissions |= PERMISSION_READ;
    user_permissions |= PERMISSION_WRITE;
    printf("\n--- After granting READ and WRITE ---\n");
    print_permissions(user_permissions);

    // SET multiple flags at once by OR-ing them together in one line
    user_permissions |= (PERMISSION_EXECUTE | PERMISSION_SHARE);
    printf("\n--- After granting EXECUTE and SHARE ---\n");
    print_permissions(user_permissions);

    // CLEAR a bit: revoke write using AND with the inverted mask
    // ~PERMISSION_WRITE = 11111101 — forces bit 1 to 0, leaves all others
    user_permissions &= ~PERMISSION_WRITE;
    printf("\n--- After revoking WRITE ---\n");
    print_permissions(user_permissions);

    // CHECK a bit: test if the user can execute
    // AND with the flag mask — non-zero means the bit is set
    if (user_permissions & PERMISSION_EXECUTE) {
        printf("\n--- Access check: EXECUTE permission GRANTED ---\n");
    }

    // TOGGLE a bit: flip SHARE using XOR
    // XOR with 1 flips the bit; XOR with 0 leaves it unchanged
    user_permissions ^= PERMISSION_SHARE;
    printf("\n--- After toggling SHARE (was ON, now OFF) ---\n");
    print_permissions(user_permissions);

    return 0;
}
▶ Output
--- Initial state ---
Permissions: [----] [-----] [-------] [------] [-----]

--- After granting READ and WRITE ---
Permissions: [READ] [WRITE] [-------] [------] [-----]

--- After granting EXECUTE and SHARE ---
Permissions: [READ] [WRITE] [EXECUTE] [------] [SHARE]

--- After revoking WRITE ---
Permissions: [READ] [-----] [EXECUTE] [------] [SHARE]

--- Access check: EXECUTE permission GRANTED ---

--- After toggling SHARE (was ON, now OFF) ---
Permissions: [READ] [-----] [EXECUTE] [------] [-----]
🔥
Interview Gold: The Three Bitwise IdiomsMemorise these three lines cold — SET: flags |= mask, CLEAR: flags &= ~mask, CHECK: flags & mask. Interviewers at embedded, systems, and game companies ask you to implement these from scratch. If you can write them without hesitation and explain WHY each one works, you immediately stand out.

Bit Shifting for Fast Math and Data Packing

Bit shifts are doing two different jobs in real codebases, and conflating them leads to bugs. The first job is fast arithmetic: shifting left by N multiplies by 2^N, shifting right by N divides by 2^N (integer division). Modern compilers do this automatically for power-of-two constants, but you'll still see explicit shifts in performance-critical hot paths and in code that needs to be portable to compilers without optimisation.

The second job is data packing and unpacking — cramming multiple values into a single integer. This is everywhere in real protocols. A 32-bit RGBA color value packs four 8-bit channels. An IPv4 header packs the version number and IHL into a single byte. An audio sample format encodes bit depth, channel count, and sample rate into a single flags integer.

The formula is always the same: shift a value LEFT into its position to pack it, shift RIGHT and mask to unpack it. The mask (typically 0xFF for an 8-bit field, 0xF for a 4-bit field) ensures you only extract the bits that belong to that field and nothing bleeds in from adjacent fields.

This packing technique is also why bitfields in structs exist — they're syntactic sugar over exactly this pattern.

color_packing.c · C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
#include <stdio.h>
#include <stdint.h>  // for uint32_t, uint8_t — always use fixed-width types for bit work

// Pack four 8-bit color channels into one 32-bit unsigned integer
// Layout: [ALPHA: bits 31-24] [RED: bits 23-16] [GREEN: bits 15-8] [BLUE: bits 7-0]
uint32_t pack_rgba(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) {
    uint32_t packed = 0;
    packed |= ((uint32_t)alpha << 24); // shift alpha into the top 8 bits
    packed |= ((uint32_t)red   << 16); // shift red into bits 23-16
    packed |= ((uint32_t)green <<  8); // shift green into bits 15-8
    packed |= ((uint32_t)blue  <<  0); // blue sits in the lowest 8 bits (no shift needed)
    return packed;
}

// Unpack: shift the target field down to bit-0, then mask off everything above it
uint8_t extract_red(uint32_t packed_color) {
    return (packed_color >> 16) & 0xFF; // shift down 16, keep only lowest 8 bits
}

uint8_t extract_green(uint32_t packed_color) {
    return (packed_color >> 8) & 0xFF;  // shift down 8, keep only lowest 8 bits
}

uint8_t extract_blue(uint32_t packed_color) {
    return packed_color & 0xFF;         // blue is already at bit 0, just mask
}

uint8_t extract_alpha(uint32_t packed_color) {
    return (packed_color >> 24) & 0xFF; // shift down 24, keep only lowest 8 bits
}

// Demonstrate fast multiply/divide with shifts
void demonstrate_shift_arithmetic(void) {
    unsigned int pixel_count = 13;
    printf("\n=== Shift Arithmetic Demo ===\n");
    printf("pixel_count          = %u\n",  pixel_count);
    printf("pixel_count << 1     = %u  (x2 = multiply by 2^1)\n",  pixel_count << 1);
    printf("pixel_count << 3     = %u  (x8 = multiply by 2^3)\n",  pixel_count << 3);
    printf("pixel_count >> 1     = %u  (÷2 = divide by 2^1)\n",   pixel_count >> 1);
}

int main(void) {
    // Pack a coral-ish color: R=255, G=100, B=80, A=200
    uint8_t r = 255, g = 100, b = 80, a = 200;
    uint32_t coral = pack_rgba(r, g, b, a);

    printf("=== RGBA Color Packing Demo ===\n");
    printf("Input  — R:%3u G:%3u B:%3u A:%3u\n", r, g, b, a);
    printf("Packed — 0x%08X (decimal: %u)\n", coral, coral);

    // Now unpack and verify we get the original values back
    printf("\n=== Unpacking Back Out ===\n");
    printf("Extracted — R:%3u G:%3u B:%3u A:%3u\n",
        extract_red(coral),
        extract_green(coral),
        extract_blue(coral),
        extract_alpha(coral)
    );

    demonstrate_shift_arithmetic();

    return 0;
}
▶ Output
=== RGBA Color Packing Demo ===
Input — R:255 G:100 B: 80 A:200
Packed — 0xC8FF6450 (decimal: 3372425296)

=== Unpacking Back Out ===
Extracted — R:255 G:100 B: 80 A:200

=== Shift Arithmetic Demo ===
pixel_count = 13
pixel_count << 1 = 26 (x2 = multiply by 2^1)
pixel_count << 3 = 104 (x8 = multiply by 2^3)
pixel_count >> 1 = 6 (÷2 = divide by 2^1)
⚠️
Watch Out: Always Cast Before ShiftingNotice the (uint32_t) casts before each left shift in pack_rgba(). Without them, you're shifting an 8-bit uint8_t, which gets promoted to int (likely 32 bits, but signed). Shifting into the sign bit of a signed int is undefined behaviour in C. Always cast to the target type before a left shift — it costs nothing and prevents nasty surprises on different architectures.

XOR Tricks and the Swap Without a Temp Variable

XOR has a remarkable mathematical property: it's its own inverse. If you XOR a value with a key to 'encode' it, XOR-ing the result with the same key gives you the original value back. This property powers simple (though not secure) data obfuscation, checksum algorithms, and one of the most famous C interview tricks: swapping two variables without a temporary.

The XOR swap works because of three applications of the inverse property. After a ^= b, the variable a holds the combination of both values. After b ^= a, b now holds the original a. After a ^= b, a now holds the original b. It reads like magic until you trace the bits.

But here's the honest truth a senior dev will tell you: don't use XOR swap in production. Modern compilers generate a three-instruction register swap that's faster, and the XOR swap silently corrupts both variables if you accidentally call it with the same memory address (i.e., xor_swap(&value, &value) zeroes the variable). It's a brilliant interview answer — and a terrible production choice.

Where XOR legitimately earns its keep is in parity checking, error detection (CRC algorithms use XOR extensively), and finding the unique element in an array where every other element appears twice — a classic algorithm problem that becomes a single-line solution with XOR.

xor_tricks.c · C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
#include <stdio.h>
#include <stdint.h>

// XOR swap — brilliant to understand, don't use in real code
void xor_swap(int *value_a, int *value_b) {
    if (value_a == value_b) return;  // CRITICAL guard — same address would zero both out
    *value_a ^= *value_b;            // a now holds 'a XOR b'
    *value_b ^= *value_a;            // b now holds 'b XOR (a XOR b)' = original a
    *value_a ^= *value_b;            // a now holds '(a XOR b) XOR a' = original b
}

// XOR's inverse property: encode then decode with the same key
void demonstrate_xor_encode(void) {
    uint8_t original_byte  = 0b10110011; // 179 — our data
    uint8_t secret_key     = 0b01101010; // 106 — our key

    uint8_t encoded = original_byte ^ secret_key;  // XOR to obscure
    uint8_t decoded = encoded ^ secret_key;         // XOR again with same key to recover

    printf("=== XOR Encoding Demo ===\n");
    printf("Original : %3u (0x%02X)\n", original_byte, original_byte);
    printf("Key      : %3u (0x%02X)\n", secret_key, secret_key);
    printf("Encoded  : %3u (0x%02X)\n", encoded, encoded);
    printf("Decoded  : %3u (0x%02X) — matches original: %s\n",
        decoded, decoded, (decoded == original_byte) ? "YES" : "NO");
}

// Classic algorithm: find the one number that appears only once
// Every other number in the array appears exactly twice
// XOR-ing a number with itself gives 0; XOR-ing with 0 gives the number itself
int find_unique_element(int *readings, int count) {
    int unique = 0;
    for (int index = 0; index < count; index++) {
        unique ^= readings[index]; // pairs cancel out (N^N=0); the lone value survives
    }
    return unique;
}

int main(void) {
    // Demonstrate XOR swap
    int temperature_celsius = 37;
    int boiling_point       = 100;
    printf("Before swap: temp=%d, boiling=%d\n", temperature_celsius, boiling_point);
    xor_swap(&temperature_celsius, &boiling_point);
    printf("After swap:  temp=%d, boiling=%d\n\n", temperature_celsius, boiling_point);

    demonstrate_xor_encode();

    // Sensor readings — every reading appears twice except one faulty sensor ID
    int sensor_readings[] = {4, 7, 2, 9, 7, 4, 2}; // 9 appears only once
    int reading_count = sizeof(sensor_readings) / sizeof(sensor_readings[0]);
    int faulty_sensor = find_unique_element(sensor_readings, reading_count);
    printf("\n=== Find Unique Sensor ID ===\n");
    printf("Faulty sensor ID: %d (appears only once in the data)\n", faulty_sensor);

    return 0;
}
▶ Output
Before swap: temp=37, boiling=100
After swap: temp=100, boiling=37

=== XOR Encoding Demo ===
Original : 179 (0xB3)
Key : 106 (0x6A)
Encoded : 217 (0xD9)
Decoded : 179 (0xB3) — matches original: YES

=== Find Unique Sensor ID ===
Faulty sensor ID: 9 (appears only once in the data)
🔥
Interview Gold: The Unique Element ProblemThe 'find the non-duplicate in an array' question is a staple at Google, Meta, and Amazon screening rounds. The brute-force answer is O(n²) with nested loops or O(n) with a hash map. The XOR answer is O(n) time and O(1) space — no extra memory at all. If you can code it in under 60 seconds and explain why it works, you've just separated yourself from 80% of candidates.
OperatorSymbolWhat It DoesCommon Use CaseResult on 0b1010 & 0b1100
AND&Bit is 1 only if BOTH inputs are 1Masking / checking flags0b1000 (8)
OR|Bit is 1 if EITHER input is 1Setting flags0b1110 (14)
XOR^Bit is 1 if inputs DIFFERToggling flags, finding duplicates0b0110 (6)
NOT~Flips every bitInverting a mask for CLEAR operation~0b1010 = 0b0101 (varies by type)
Left Shift<<Shifts bits left, fills with 0sMultiply by 2^n, building masks0b1010 << 1 = 0b10100 (20)
Right Shift>>Shifts bits rightDivide by 2^n, extracting fields0b1010 >> 1 = 0b0101 (5)

🎯 Key Takeaways

  • The three bitwise idioms — SET with |=, CLEAR with &= ~mask, CHECK with & mask — appear in virtually every embedded, systems, and game codebase. Knowing them cold is non-negotiable.
  • Always use fixed-width unsigned types (uint8_t, uint16_t, uint32_t from stdint.h) for bit manipulation. Signed types and variable-width types like int are a minefield of undefined and implementation-defined behaviour.
  • Left shift by N multiplies by 2^N; right shift by N divides by 2^N. But these are only safe and predictable on unsigned values — right-shifting signed negatives is implementation-defined in C.
  • XOR's self-inverse property (a ^ b ^ b == a) enables the unique-element algorithm, simple toggle logic, and basic data obfuscation — and it's a favourite interview topic because the elegant O(1)-space solution is non-obvious until you understand what XOR actually does to bits.

⚠ Common Mistakes to Avoid

  • Mistake 1: Confusing & (bitwise AND) with && (logical AND) — Using & when you mean && in an if-condition gives a nonsensical result because & operates on raw bits, not boolean truth. For example, if (permission_flags & is_logged_in) evaluates a bitwise AND of an integer and a boolean — it may pass or fail for completely wrong reasons. Fix: Use && for boolean logic (true/false conditions) and & exclusively for bit manipulation. If you're checking a flag, if (flags & MASK) is correct — but don't mix flag-checking with logical conditions in one expression without careful parentheses.
  • Mistake 2: Right-shifting a signed negative integer and expecting zeros to fill — On virtually all modern platforms, right-shifting a signed negative value fills with 1-bits (arithmetic shift), not 0-bits (logical shift). So int value = -8; value >> 1; gives -4, not 2147483644. The C standard calls this implementation-defined behaviour. Fix: Always use unsigned integers (unsigned int, uint32_t, etc.) for bit manipulation. Add a cast if needed: (unsigned int)signed_value >> shift_amount. This guarantees logical (zero-filling) right shift behaviour.
  • Mistake 3: Applying NOT (~) without masking and getting unexpected large values — ~ flips ALL bits including the upper bits of a wider type. If your variable is an unsigned int (32 bits) and you do ~0xFF, you get 0xFFFFFF00 (4294967040), not 0x00 as beginners expect. This is the root cause of hundreds of permission-flag bugs. Fix: Always AND the result of NOT with a bitmask that limits it to your intended width: (~PERMISSION_WRITE) & 0xFF if working with byte-sized flags, or define your masks and variables as the same fixed-width type from the start.

Interview Questions on This Topic

  • QHow would you check if a given integer is a power of two using bitwise operators? Can you explain why your solution works at the bit level? (Answer hint: a power of two in binary has exactly one 1-bit set, so n & (n - 1) equals zero for any power of two — and you must add a check for n > 0 since zero would also pass the test.)
  • QGiven a permissions system where each permission is a bit flag, write the code to grant a permission, revoke a permission, and check if a permission is active. Walk me through exactly what happens to the bits in each operation.
  • QWhat's the difference between arithmetic right shift and logical right shift? Which does C guarantee for unsigned types, and what does it say about signed types? Why does this matter in practice?

Frequently Asked Questions

What is the difference between bitwise AND (&) and logical AND (&&) in C?

Bitwise AND (&) operates on every individual bit of both operands and produces an integer result. Logical AND (&&) treats both sides as true/false conditions, short-circuits if the left side is false, and always returns 0 or 1. Use & for manipulating bit flags and && for combining boolean conditions in if-statements — mixing them up is a classic source of subtle bugs.

When should I use bitwise operators instead of regular arithmetic?

Use bitwise operators when you need to pack multiple values into a single integer (like flags, pixel channels, or protocol fields), when you're working with hardware registers or memory-mapped I/O, or when you need the absolute fastest possible multiply/divide by a power of two. For general arithmetic, let the compiler do its job — it will use bit shifts internally where appropriate.

Why does ~0 give -1 in C instead of 0?

Because integers in C are stored in two's complement representation. The bit pattern for 0 is all zeros. NOT flips every bit, giving all ones. In two's complement, all-ones is the representation of -1. This is why ~0 == -1 for any signed integer type, and why you should mask the result of NOT when working with bit flags: use (~YOUR_FLAG & 0xFF) to stay within your intended bit width.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousPreprocessor Directives in CNext →Inline Functions in C++
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged