C Programming · September 11, 2026

Bitwise and Special Operators in C

Module I · Basics of C · Lecture 8


🎯 Why This Matters

Every value in C — no matter its type — is ultimately just a pattern of bits in memory, as the “Data Types in C” post explained. Most of the operators you’ve used so far (+>&&) treat that pattern as a single number or a single true/false value. Bitwise operators are different: they reach inside a value and work on its individual bits, one by one. This is how real systems pack multiple settings into a single number, talk to hardware, and squeeze out extra performance — skills this lecture’s short quiz and coding task will test directly.

🔢 A Quick Refresher: Numbers as Bits

Recall from the Data Types post that an int is typically 4 bytes — 32 individual bits, each either 0 or 1. The number 22, for example, is stored as ...00010110 in binary. Bitwise operators look at this pattern directly, rather than at the number it represents.

#include <stdio.h>

int main() {
    int x = 22;
    printf("Size of int: %zu bytes = %zu bits\n", sizeof(x), sizeof(x) * 8);
    return 0;
}

🖥️ Output:
Size of int: 4 bytes = 32 bits

🔀 The Six Bitwise Operators

OperatorNameWhat it does
&ANDSets a bit to 1 only if both corresponding bits are 1
|ORSets a bit to 1 if either corresponding bit is 1
^XORSets a bit to 1 if the two corresponding bits differ
~NOT (complement)Flips every bit — the only unary bitwise operator
<<Left shiftSlides every bit left, filling with zeros on the right
>>Right shiftSlides every bit right

⚠️ Easy to confuse: & and && look similar but do completely different jobs. && (covered in the last post) combines two whole true/false values and short-circuits. & combines two numbers bit by bit and always evaluates both sides. The same distinction applies to | versus ||.

Bitwise AND (&)

#include <stdio.h>

int main() {
    int a = 0x5C;   // 0101 1100
    int b = 0x3A;   // 0011 1010

    int result = a & b;
    printf("a & b = %d (0x%X)\n", result, result);

    return 0;
}

🖥️ Output:
a & b = 24 (0x18)

Line up the two patterns and compare column by column: a 1 appears in the result only where both inputs have a 1 in that exact position. Every other column becomes 0. This is the single most useful bitwise operator — it’s how you check or extract specific bits from a larger value.

Bitwise OR (|) and XOR (^)

#include <stdio.h>

int main() {
    int a = 0x5C;   // 0101 1100
    int b = 0x3A;   // 0011 1010

    printf("a | b = %d (0x%X)\n", a | b, a | b);
    printf("a ^ b = %d (0x%X)\n", a ^ b, a ^ b);

    return 0;
}

🖥️ Output:
a | b = 126 (0x7E)
a ^ b = 102 (0x66)

| turns a bit on if either input has it on — it’s used to combine settings together. ^ is more subtle: it turns a bit on only where the two inputs disagree. A useful side effect: x ^ x is always 0, and XOR-ing the same value twice always cancels back to the original — a property put to use in some classic bit-toggling tricks later in this post.

Bitwise NOT (~)

#include <stdio.h>

int main() {
    unsigned char a = 0x0F;   // 0000 1111

    printf("a  = %u (0x%X)\n", a, a);
    printf("~a = %u (0x%X)\n", (unsigned char)~a, (unsigned char)~a);

    return 0;
}

🖥️ Output:
a = 15 (0xF)
~a = 240 (0xF0)

~ is the only bitwise operator that takes a single operand — it flips every single bit, turning every 0 into a 1 and every 1 into a 0. Notice this example uses unsigned char (1 byte) specifically so the flipped pattern is easy to read; applying ~ to a full 4-byte int would flip all 32 bits, most of which would just show up as a large negative number when printed as signed.

↔️ Shift Operators

The shift operators slide every bit in a value left or right by a given number of positions — and because of how binary numbers work, this has a beautifully simple side effect.

#include <stdio.h>

int main() {
    int x = 22;
    int y = 88;

    printf("x << 2 = %d\n", x << 2);
    printf("y >> 2 = %d\n", y >> 2);

    return 0;
}

🖥️ Output:
x << 2 = 88
y >> 2 = 22

Shifting left by n positions is the same as multiplying by 2ⁿ, and shifting right by n is the same as integer-dividing by 2ⁿ — 22 << 2 is exactly 22 × 4 = 88. Compilers historically used this trick to replace multiplication with faster shift instructions; today’s compilers do this automatically, but the equivalence is still worth knowing, since shifts show up constantly in real embedded and systems code.

⚠️ A genuine warning: Shifting a signed value that’s already negative, or shifting by a number of bits greater than or equal to the type’s width (e.g. x << 40 on a 32-bit int), is undefined behavior in C. Stick to unsigned types and small, sensible shift amounts unless you know exactly what you’re doing.

🚩 A Real Application: Bit Flags

One of the most common real-world uses of bitwise operators is packing several yes/no settings into a single number — each bit acting as its own independent on/off switch, called a flag.

#include <stdio.h>

#define READ    4   // 0000 0100
#define WRITE   2   // 0000 0010
#define EXECUTE 1   // 0000 0001

int main() {
    int permissions = READ | WRITE | EXECUTE;
    printf("Permissions value: %d\n", permissions);

    int canWrite = permissions & WRITE;
    printf("Has write access: %d\n", canWrite);

    int readOnly = permissions & ~WRITE;
    printf("Permissions after removing write: %d\n", readOnly);

    return 0;
}

🖥️ Output:
Permissions value: 7
Has write access: 2
Permissions after removing write: 5

Three techniques worth naming individually, since you’ll see this exact pattern throughout real C code:

  • Setting a flag: flags | FLAG turns a specific bit on without disturbing any others.
  • Checking a flag: flags & FLAG gives a non-zero result only if that specific bit is set.
  • Clearing a flag: flags & ~FLAG turns a specific bit off — ~WRITE flips every bit except the write bit, so ANDing with it keeps everything except write access.

💡 Where this actually shows up: This is precisely how Unix file permissions work (chmod 7 means read+write+execute, exactly as shown above), and it’s the standard way embedded systems pack multiple hardware settings into a single control register — one byte, eight independent switches.

🎓 More Practical Bitwise Tricks

Checking Odd or Even Without %

#include <stdio.h>

int main() {
    int n = 17;

    int isOdd = n & 1;
    printf("Is odd: %d\n", isOdd);

    n = 24;
    isOdd = n & 1;
    printf("Is odd: %d\n", isOdd);

    return 0;
}

🖥️ Output:
Is odd: 1
Is odd: 0

In binary, only the very last bit determines whether a number is odd or even — every odd number ends in 1, every even number ends in 0. ANDing with 1 isolates exactly that bit, giving a faster alternative to n % 2 that’s still used in performance-critical code today.

Swapping Two Variables with XOR

#include <stdio.h>

int main() {
    int a = 12, b = 25;
    printf("Before: a = %d, b = %d\n", a, b);

    a = a ^ b;
    b = a ^ b;
    a = a ^ b;

    printf("After:  a = %d, b = %d\n", a, b);

    return 0;
}

🖥️ Output:
Before: a = 12, b = 25
After: a = 25, b = 12

This is a classic party trick: swapping two variables using XOR, without needing a third temporary variable like the swap program from the Data Types exercise sheet. It works because of that self-cancelling property mentioned earlier — x ^ x = 0. In practice, a plain temporary variable is still clearer and just as fast on modern compilers, but recognizing this pattern is a genuine rite of passage in C.

✳️ Special Operators: A Quick Tour

Beyond arithmetic, relational, logical, and bitwise operators, C has a handful of operators that don’t fit neatly into any single category. Two are worth introducing now.

sizeof — Already Familiar

You’ve used sizeof throughout this course to check a type’s size in bytes — it’s technically classified as a special/unary operator, not a function, which is why it can be written without parentheses in most contexts.

#include <stdio.h>

int main() {
    int x = 100;
    printf("sizeof x       = %zu\n", sizeof x);
    printf("sizeof(int)    = %zu\n", sizeof(int));
    printf("sizeof(double) = %zu\n", sizeof(double));
    return 0;
}

🖥️ Output:
sizeof x = 4
sizeof(int) = 4
sizeof(double) = 8

The Comma Operator

#include <stdio.h>

int main() {
    int a, b, sum;

    sum = (a = 5, b = 10, a + b);
    printf("sum = %d\n", sum);

    return 0;
}

🖥️ Output:
sum = 15

The comma operator evaluates several expressions in sequence, from left to right, and the entire comma-separated group evaluates to the value of the last one. Here, a gets 5, then b gets 10, and the whole parenthesized group evaluates to a + b, which is what gets assigned to sum. You’ll meet this operator again once we reach for loops, where it’s commonly used to update two counters in a single loop header.

🚫 Common Mistakes Beginners Make

MistakeWhy it happens
Using & or | where && or || was meantBitwise operators compare individual bits, not whole true/false values — they don’t short-circuit either
Expecting ~5 to just mean “not five”~ flips every bit of the value’s full binary representation, usually producing a large or negative-looking number
Shifting a negative number or shifting too farBoth are undefined behavior in C — stick to unsigned values and shift amounts smaller than the type’s bit width
Forgetting parentheses around a bitwise expression mixed with comparisons& and | have lower precedence than ==, so x & 1 == 1 doesn’t do what it looks like — it evaluates 1 == 1 first. Always write (x & 1) == 1.

🎓 Practice Exercises

Exercise 1: Predict the Output

int a = 6, b = 3;
printf("%d\n", a & b);
printf("%d\n", a | b);
printf("%d\n", a ^ b);

(Answer: 275 — trace 6 = 0110 and 3 = 0011 column by column for each operator.)

Exercise 2: Trace the Shift

int x = 5;
printf("%d\n", x << 3);

(Answer: 40 — shifting left by 3 is the same as multiplying by 2³ = 8, and 5 × 8 = 40.)

Exercise 3: Build It Yourself

Using the READWRITE, and EXECUTE flags from this post, write a program that starts with full permissions (7), removes the EXECUTE flag using & and ~, and then prints whether READ access is still present.

❓ Frequently Asked Questions

Q: When would I actually use bitwise operators in a real program, outside of a course exercise?
Anywhere memory or bandwidth is tight and settings need to be packed efficiently — embedded systems reading hardware registers, network protocols packing multiple fields into a few bytes, graphics code manipulating color channels, and permission systems like the Unix file example in this post.

Q: Is there a bitwise equivalent of the logical NOT (!) that only flips truthiness, not every bit?
Not directly — ~ always flips the full bit pattern. If you specifically want “is this value zero or non-zero, flipped,” that’s exactly what ! already does; the two operators solve genuinely different problems and aren’t meant to substitute for each other.

Q: Why does right-shifting a negative number behave unpredictably?
Negative numbers are stored using a representation called two’s complement (a topic for a later, more advanced post), and different compilers are allowed to fill the vacated bits differently when right-shifting a negative value — which is exactly why the C standard leaves it undefined. Stick to unsigned types whenever you’re deliberately manipulating bits.

✅ Key Takeaways

  • Bitwise operators (& | ^ ~ << >>) work on the individual bits of a value, not on the value as a whole — a completely different job from the logical operators covered in the previous post.
  • & is used to check or extract specific bits, | to set them, and & ~ together to clear them — the three core operations behind bit-flag systems like Unix permissions.
  • Shifting left by n multiplies by 2ⁿ; shifting right by n divides by 2ⁿ — a direct consequence of how binary place value works.
  • sizeof and the comma operator are two “special” operators that don’t fit the arithmetic/relational/logical/bitwise categories, but show up regularly in real C code.
  • Bitwise and logical operators look similar (& vs &&| vs ||) but are never interchangeable — mixing them up is a common and often silent bug.

🚀 Next up: With every category of operator now covered, we’ll finally put them to work inside real decision-making — control structures, starting with ifelse if, and else, and how C actually branches program flow based on everything we’ve built toward across this operators series.