Skip to content
VLSI Mentor

C Part I · Module 3 · Expressions and Operators

Bitwise Operators

Every operator so far has treated a value as a single quantity. These four treat it as a row of independent bits and operate on each position separately, which is a genuinely different way of thinking about the same storage. Module 2 spent four chapters making that possible — bits, bytes, hexadecimal notation, and the difference between a value and its representation — and this is where that work is spent. The operators themselves are simple enough to learn in ten minutes. What takes longer, and matters more, is the small set of idioms built from them for testing, setting, clearing and toggling individual bits, because those four patterns are how a great deal of systems and hardware-facing code is actually written.

Foundation14 min readCPart IFundamentalsRepresentation

Part I · Module 3 · Chapter 3.8 · Bitwise Operators

1. A Different Way to Read the Same Value

When you write x + 1, C treats x as a single number. The bitwise operators do something else: they line the two operands up bit by bit and produce a result one position at a time.

Chapter 2.4 established that a value has a representation — a row of bits — and that hexadecimal exists precisely so you can read that row. This chapter is where you operate on it.

A note on types before starting. Every example here uses unsigned int, and every literal carries a u suffix. That follows Chapter 2.6's guidance: unsigned is the home for values that are bit patterns rather than quantities, and it keeps the patterns readable by removing the sign bit from the discussion entirely.

2. The Four Operators

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  a & b     AND  — a bit is 1 only where BOTH operands have 1
  a | b     OR   — a bit is 1 where EITHER operand has 1
  a ^ b     XOR  — a bit is 1 where the operands DIFFER
  ~a        NOT  — every bit is flipped

The first three take two operands; ~ takes one. Position by position, the rules are:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  a   b   a&b   a|b   a^b        a   ~a
  0   0    0     0     0         0    1
  0   1    0     1     1         1    0
  1   0    0     1     1
  1   1    1     1     0

Here they are on a real value. Take 0xB6, which Chapter 2.4's grouping rule reads as 1011 0110:

Azvya Education Pvt. Ltd.VLSI Mentor
all four, on one value
#include <stdio.h>

int main(void)
{
    unsigned int x = 0xB6u;
    unsigned int mask = 0x0Fu;

    printf("x        = %u\n", x);
    printf("x & mask = %u\n", x & mask);
    printf("x | 0x40 = %u\n", x | 0x40u);
    printf("x ^ 0xFF = %u\n", x ^ 0xFFu);
    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x        = 182
x & mask = 6
x | 0x40 = 246
x ^ 0xFF = 73

The decimal output is not the interesting part. Work each one in bits, which is what Chapter 2.4 trained you to do:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  AND — keep only where both have 1
        x        1011 0110      0xB6
        mask     0000 1111      0x0F
                 ---------
        x & mask 0000 0110      0x06  = 6

  OR — set where either has 1
        x        1011 0110      0xB6
        0x40     0100 0000      0x40
                 ---------
        result   1111 0110      0xF6  = 246

  XOR — 1 where they differ
        x        1011 0110      0xB6
        0xFF     1111 1111      0xFF
                 ---------
        result   0100 1001      0x49  = 73

Each column was decided on its own, with no carrying and no interaction between positions. That is the whole mechanism.

3. ~ Is Wider Than You Expect

~ flips every bit — but every bit of what? The answer catches nearly everyone:

~ inverts every bit of its operand at the operand's full width, not at the width of the digits you happened to type.

An unsigned int on a typical machine is 32 bits, not 8. So 0x0Fu is not 0000 1111 in storage; it is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  0x0Fu   0000 0000 0000 0000 0000 0000 0000 1111
  ~0x0Fu  1111 1111 1111 1111 1111 1111 1111 0000    = 0xFFFFFFF0u

The expected answer — 0xF0 — would only be right if the operand were 8 bits wide. It is not.

This is Chapter 2.5's point arriving in a new place: the width of a type is a property of your implementation, not of how you wrote the literal. If you want only the low eight bits of an inverted value, you have to say so, which is what & is for:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ~0x0Fu & 0xFFu   →  0xF0u

4. The Four Idioms

The operators are a means. These four patterns are what you actually write, and they are worth knowing by name.

Throughout, a mask is a value with 1 in the positions you care about and 0 everywhere else.

Test — is a bit set?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  (value & mask) != 0

& keeps only the positions the mask selects. If any of them were set, the result is non-zero.

Note the explicit comparison against 0. Chapter 3.5 explained why: value & mask produces a pattern, not a truth value, and while any non-zero pattern is true, comparing it against 1 would be wrong.

Extract — what is in these positions?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  value & mask

Same operation, different intent: instead of asking whether anything is there, you keep the selected bits and discard the rest. x & 0x0Fu in §2 kept the low four bits of x and gave 0x06. This is the operation the curriculum's own annotation describes as keep the low four bits.

Set — turn these bits on, leave the rest alone

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  value | mask

| puts 1 wherever the mask has 1 and leaves every other position exactly as it was.

Clear — turn these bits off, leave the rest alone

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  value & ~mask

Invert the mask so it is 0 in the positions you want gone and 1 everywhere else, then &. Chapter 2.7's advice applies: read ~mask as everything except the mask.

Toggle — flip these bits

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  value ^ mask

^ gives 1 where the operands differ, so a 1 in the mask flips that position and a 0 leaves it alone.

Here are all four applied to one value:

Azvya Education Pvt. Ltd.VLSI Mentor
test, set, clear, toggle
#include <stdio.h>

int main(void)
{
    unsigned int flags = 0x0Au;      /* 0000 1010 */
    unsigned int bit1 = 0x02u;       /* 0000 0010 */
    unsigned int bit2 = 0x04u;       /* 0000 0100 */

    printf("bit1 set?  %d\n", (flags & bit1) != 0u);
    printf("bit2 set?  %d\n", (flags & bit2) != 0u);
    printf("set bit2   %u\n", flags | bit2);
    printf("clear bit1 %u\n", flags & ~bit1);
    printf("toggle bit1 %u\n", flags ^ bit1);
    printf("flags unchanged %u\n", flags);
    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bit1 set?  1
bit2 set?  0
set bit2   14
clear bit1 8
toggle bit1 8
flags unchanged 10

Check them in bits: flags is 0000 1010. Bit 1 is set, bit 2 is not. Setting bit 2 gives 0000 1110 = 14. Clearing bit 1 gives 0000 1000 = 8. Toggling bit 1 also gives 8, because it was set and got flipped off.

And the last line matters: none of these operators changed flags. Chapter 3.1's rule holds here as everywhere — they read their operands and produced new values. To change the object you assign, and Chapter 3.4's compound forms exist for exactly this:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
flags |= bit2;      /* set bit 2 in flags */
flags &= ~bit1;     /* clear bit 1 in flags */
flags ^= bit1;      /* toggle bit 1 in flags */

5. Do Not Confuse Them With the Logical Operators

Chapter 3.6 raised this and it is worth repeating from the other side, because the failure is quiet:

& and |&& and ||
Operate oneach bit separatelythe whole value's truth
Producea bit patternint 1 or 0
Both operands evaluatedalwaysnot necessarily

1 & 2 is 0, because 0001 and 0010 share no set bit. 1 && 2 is 1, because both values are non-zero and therefore true. Same-looking expressions, completely different questions.

There is also a precedence trap here — & binds more loosely than ==, so flags & mask == 0 does not group the way it reads. Chapter 3.10 covers that properly and gives the rule; the short version is that the parentheses in (flags & mask) != 0 above are not decoration.

6. What You Should Be Able to Say Now

  1. Bitwise operators work on each bit position independently, with no carrying between positions.
  2. & keeps bits set in both, | sets bits set in either, ^ sets bits where they differ, ~ flips every bit.
  3. Use unsigned types for bit patterns, as Chapter 2.6 advised.
  4. ~ inverts at the operand's full width, so ~0x0Fu is 0xFFFFFFF0u on a 32-bit unsigned int, not 0xF0u.
  5. To limit a result to a width, mask it explicitly.
  6. Test: (value & mask) != 0. Extract: value & mask. Set: value | mask. Clear: value & ~mask. Toggle: value ^ mask.
  7. Compare a masked result against 0, never against 1 — it is a pattern, not a truth value.
  8. These operators do not change their operands. Use |=, &=, ^= to change an object.
  9. & is not &&. Different question, different result, different evaluation rules.

7. Exercise

All values 8 bits wide for the hand-working questions.

Exercise 1 — work them in bits. Given a = 0xCCu and b = 0x0Fu, compute by hand and then check by running: a & b, a | b, a ^ b.

Exercise 2 — predict the width trap. What is ~0x00u as an unsigned int on a machine where that type is 32 bits? Give the hexadecimal answer and explain it in one sentence.

Exercise 3 — choose the idiom. For each goal, write the expression using value and mask: turn the masked bits on; turn them off; flip them; find out whether any are on; keep only them.

Exercise 4 — build a mask. Using Chapter 2.4's notation, write the hexadecimal mask that selects: only bit 0; only bit 7; bits 0 through 3; bits 4 through 7.

Exercise 5 — find the bug. This is meant to clear bit 3 and does not. Say what it actually does and give the correction:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unsigned int flags = 0xFFu;
unsigned int bit3 = 0x08u;
flags = flags & bit3;

Exercise 6 — distinguish the operators. Compute 6 & 3 and 6 && 3 and explain in two sentences why they differ. Then say which one you would use to combine two range checks.

8. Summary

  • Bitwise operators treat a value as independent bits, deciding each position on its own.
  • & masks, | sets, ^ toggles, ~ inverts.
  • Use unsigned types for patterns so the sign bit is not part of the discussion.
  • ~ inverts the full width of the type, which is why ~0x0Fu is not 0xF0u. Mask explicitly when you want a narrower result.
  • Five idioms carry nearly all real use: test, extract, set, clear, toggle.
  • Compare masked results against 0, never 1.
  • The operators produce values; they do not modify objects. |=, &= and ^= do that.
  • & is not &&, and the difference is not cosmetic.

These four operators work on bits where they already sit. The next chapter adds the two that move bits between positions — which sounds simpler and is in fact the place in this module where C's rules are tightest, with several combinations of operand and count where the language declines to define a result at all.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.