C Mastery / Operators: Bitwise and Shift
Part 1 — The Core Language

Operators: Bitwise and Shift

This chapter covers the bitwise operators (&, |, ^, ~) and the shift operators (<<, >>). These are essential for systems, embedded, and performance work, and their edge cases are a rich source of undefined behavior.

Why This Matters

Bit manipulation is how you talk to hardware, pack data into protocol fields, optimize certain computations, and implement flags and bitsets. Getting the shift rules wrong produces subtle, hard-to-find bugs.

Prerequisites

Core Concept

Bitwise operators

OperatorNameEffect
&bitwise ANDbit is 1 if both corresponding bits are 1
``bitwise ORbit is 1 if either corresponding bit is 1
^bitwise XORbit is 1 if exactly one corresponding bit is 1
~one's complementflips every bit

These operate on the integer types after the integer promotions (c.types.promotion).

Shift operators

OperatorNameEffect
<<left shiftshifts bits left, filling with zeros
>>right shiftshifts bits right (fill depends on type)

For unsigned types, x >> n is a logical shift (fills with zeros). For signed non-negative values, x >> n fills with zeros too (because the value is non-negative). For signed negative values, the result of right shift is implementation-defined in C17 and earlier (arithmetic shift fills with ones on most platforms). C23 requires arithmetic shift for negative signed values. STANDARD-VERSION-DEPENDENT

Syntax

unsigned a = 0x0F, b = 0x3C;

a & b;   /* 0x0C */
a | b;   /* 0x3F */
a ^ b;   /* 0x33 */
~a;      /* 0xFFFFFFF0 (for 32-bit unsigned int) */

a << 2;  /* 0x3C */
b >> 2;  /* 0x0F */

Examples

Setting, clearing, toggling, testing bits

#include <stdio.h>

int main(void)
{
    unsigned flags = 0;

    flags |=  (1u << 3);   /* set bit 3 */
    flags &= ~(1u << 3);   /* clear bit 3 */
    flags ^=  (1u << 3);   /* toggle bit 3 */
    int is_set = (flags & (1u << 3)) != 0;  /* test bit 3 */

    printf("%u %d\n", flags, is_set);
    return 0;
}

Use 1u (unsigned) for the mask to avoid signed shift issues and to keep the type consistent.

Masking a range of bits

unsigned field = (value >> 4) & 0x3F;   /* extract bits 4..9 (6 bits) */

Right shift on signed values

#include <stdio.h>

int main(void)
{
    int n = -8;
    printf("%d\n", n >> 1);   /* implementation-defined for negative */
    return 0;
}

On virtually all modern platforms this prints -4 (arithmetic shift), but the C17 standard does not require it. Use unsigned types for shifts whose result you need to be portable.

How It Works

Bitwise operators act on the individual bits of the (promoted) operands. Shift operators move bits by the shift count. Left shift always fills vacated low bits with zeros; right shift fills high bits with zeros for unsigned (and for non-negative signed) values.

Variations

Arithmetic vs. logical shift

powers of two).

C guarantees logical behavior for unsigned and non-negative signed right shifts; for negative signed right shifts it is implementation-defined before C23.

Common Mistakes

(~0u).

Undefined Behavior

left operand, is UB. VERIFIED

conditions. STANDARD-VERSION-DEPENDENT

representable) is UB.

UB, but C23 makes it arithmetic. STANDARD-VERSION-DEPENDENT

Portability

UB.

defined; do not hard-code 32 or 64 for shift bounds.

Under the Hood

Shift instructions (shl, shr, sar, and their ARM equivalents) exist directly in hardware. The compiler emits the appropriate instruction; sar (arithmetic shift right) is used for signed values on x86, while shr (logical) is used for unsigned.

Practical Usage

write the arithmetic, not the shift, for clarity).

Exercises

1. Write functions set_bit, clear_bit, toggle_bit, and test_bit that operate on an unsigned int. Test each. 2. Extract a 4-bit field from a byte and re-insert it, using only masks and shifts. 3. Demonstrate with UBSan what happens when you shift by 32 on a 32-bit int. 4. Write a function that counts set bits (popcount) using a simple loop.

Deep Challenge

Implement a portable popcount and a portable "rotate left" function (rol) that work on unsigned int without relying on the width being 32, and without invoking undefined behavior for any input. Explain each boundary condition you handled.

References

Verification

in C23. STANDARD-VERSION-DEPENDENT

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.ops.bitwise06
c.ops.shift06