Operator precedence bugs are the worst kind: the code looks correct, it compiles without warnings, and it fails silently. You need to know which operators bind tighter than which, or you need to parenthesize aggressively. This article focuses on the rules that actually cause real bugs.

The Full Precedence Table (Condensed)

PrecedenceOperatorsAssociativity
1 (highest)() [] . -> ++ -- (postfix)Left
2! ~ ++ -- (cast) * & sizeof (prefix)Right
3* / %Left
4+ -Left
5<< >>Left
6< <= > >=Left
7== !=Left
8&Left
9^Left
10|Left
11&&Left
12||Left
13?:Right
14= += -= *= etc.Right
15 (lowest),Left

Bug 1: Bitwise AND Before Equality

#include <stdio.h>

int main() {
    int flags = 0x0F;
    int mask  = 0x01;

    /* WRONG: evaluated as flags & (mask == 1) = flags & 1 = 1 ✓ by accident */
    /* But really parsed as: flags & (mask == 1) */
    if (flags & mask == 1) {
        printf("bit 0 setn");
    }

    /* CORRECT: explicit parentheses */
    if ((flags & mask) == 1) {
        printf("bit 0 setn");
    }

    return 0;
}

== (precedence 7) binds tighter than & (precedence 8). So flags & mask == 1 is parsed as flags & (mask == 1). In this case mask == 1 is true (1), and 0x0F & 1 == 1 is accidentally correct — but for mask == 2, you would get flags & 0 which is always 0. GCC warns about this with -Wall.

Bug 2: sizeof on an Expression vs Type

#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};

    /* WRONG: sizeof applied to the result of (arr) / sizeof(int) */
    /* Parsed as: sizeof(arr / sizeof(int)) */
    size_t n = sizeof arr / sizeof(int);    /* correct if no parens: sizeof applied to arr */

    /* What you really mean: */
    size_t correct = sizeof(arr) / sizeof(arr[0]);

    printf("n=%zu correct=%zun", n, correct);   /* both 5 on this platform — coincidence */
    return 0;
}

sizeof expr (without parentheses) applies to the expression. sizeof(type) requires parentheses. When the expression involves operators, the precedence of those operators applies. Always use sizeof(arr) / sizeof(arr[0]) to count array elements, and always put parentheses around the entire sizeof operand.

Bug 3: Address-of Versus Multiplication

#include <stdio.h>

int main() {
    int x = 5, y = 10;
    int *p = &x;

    /* What does *p++ do? */
    printf("%dn", *p++);   /* prints 5, then increments p — NOT increments *p */
    /* Parsed as: *(p++) — postfix ++ binds tighter than dereference */

    /* To increment the value at p: */
    (*p)++;   /* explicit grouping */

    return 0;
}

Bug 4: Assignment in Conditions

int c;

/* INTENDED: if c equals EOF */
if (c = getchar() == EOF) {   /* WRONG */
    /* Parsed as: if (c = (getchar() == EOF)) */
    /* c gets the boolean result (0 or 1), not the character */
}

/* CORRECT: parenthesize the assignment */
if ((c = getchar()) == EOF) {
    /* c gets the character, then compared to EOF */
}

Bug 5: Ternary vs Assignment

#include <stdio.h>

int main() {
    int x = 5;
    int result;

    /* WRONG: parsed as result = (x > 0 ? 1 : -1 = 0) — assignment to rvalue */
    /* Actually a compile error in this case, but the precedence trap is real */

    /* Ternary ?: has lower precedence than most operators */
    /* but higher than = */
    result = x > 0 ? 1 : -1;   /* fine — = is lowest, ternary in the middle */

    /* Tricky: multiple ternaries chain right-to-left */
    int a = 1, b = 2, c = 3;
    int m = a > b ? a : b > c ? b : c;  /* (a>b) ? a : ((b>c) ? b : c) */
    printf("%dn", m);   /* 3 — max of a, b, c */

    return 0;
}

Bug 6: Chained Comparisons

#include <stdio.h>

int main() {
    int x = 5;

    /* WRONG: looks like a range check, is not */
    if (1 < x < 10) {
        printf("in rangen");   /* always prints — (1 < x) is 1, then 1 < 10 is true */
    }

    /* CORRECT: */
    if (1 < x && x < 10) {
        printf("in rangen");
    }

    return 0;
}

Unlike Python, C does not support chained comparisons. 1 < x < 10 is parsed as (1 < x) < 10. The first comparison returns 0 or 1; then 0 < 10 or 1 < 10 is always true. This is a valid C expression that compiles without warnings but never works as a range check.

The Safe Rule

If you are unsure about precedence, add parentheses. Code that is slightly over-parenthesized is better than code that is subtly wrong. The only operators where experienced programmers rely on precedence implicitly are */ before +-, and postfix before prefix operators. Everything else — bitwise ops, comparisons, assignments in conditions — gets explicit parentheses in readable code.

Test your precedence understanding in our c compiler — write an expression, then print it with and without parentheses to verify they produce the same result. Our guide on bitwise operators in C covers the precedence rules specific to bit manipulation in more detail.

TL;DR

  • & | ^ have lower precedence than == != — always parenthesize: (x & mask) == 1
  • *p++ increments the pointer, not the value — use (*p)++ to increment the value
  • Assignment in a condition needs parentheses: if ((c = getchar()) == EOF)
  • Chained comparisons don’t work in C: 1 < x < 10 is never a range check
  • Ternary chains right-to-left; nest them with explicit parentheses for clarity
  • When unsure, parenthesize — over-parenthesized code is correct; under-parenthesized code might not be