5.1.5 · D5C Programming
Question bank — Operator precedence — full table
True or false — justify
Answer with the reason, never a bare yes/no.
* having higher precedence than + means b*c is computed before a in a + b*c.
False. Higher precedence fixes grouping (
a + (b*c)), not the evaluation order; the compiler may compute a first. Precedence shapes the tree, sequence points shape the timeline.a - b - c and a - (b - c) are the same because subtraction is subtraction.
False.
- is left-associative, so a - b - c means (a - b) - c. Subtraction is not associative, so the two groupings give different numbers whenever c != 0.In x & 1 == 0, the & grabs x and 1 first because it looks like the "main" operation.
False.
== (lvl 7) outranks & (lvl 8), so it parses as x & (1 == 0). The visually central operator is not the tightest-binding one.a = b = 0 assigns a first, then b.
False. Assignment is right-associative:
a = (b = 0). The inner b = 0 runs first and returns the value 0, which is then assigned to a.a || b && c is grouped left-to-right as (a || b) && c.
False.
&& (lvl 11) binds tighter than || (lvl 12), so it groups as a || (b && c) — "AND before OR", just like "multiply before add".Because << looks like a "big" operator, it beats + in 1 << 2 + 3.
False.
+ (lvl 4) outranks << (lvl 5), giving 1 << (2+3) = 1 << 5 = 32. Shift sits below arithmetic — a classic surprise.Parentheses are technically an operator with the highest precedence in C.
True.
() grouping sits at level 1, so it overrides every other operator. That is exactly why parenthesising is the universal fix for precedence traps.The comma , operator has the lowest precedence of all C operators.
True. It sits at level 15, below even assignment. So
a = 1, 2 parses as (a = 1), 2 — a becomes 1, and the whole expression yields 2.-a++ negates first, then increments.
False. Postfix
++ (lvl 1) binds tighter than unary - (lvl 2), so it is -(a++): a++ yields the old value, which is then negated.Ternary a ? b : c ? d : e groups as (a ? b : c) ? d : e.
False.
?: is right-associative, so it is a ? b : (c ? d : e) — a clean if / else-if / else chain, not a nested condition.Spot the error
Say what the writer assumed and why it breaks.
if (flags & MASK == MASK) — the author wants "is the MASK bit set".
== beats &, so this is flags & (MASK == MASK) = flags & 1, which only tests bit 0, not the mask. Fix: if ((flags & MASK) == MASK).return a << 1 + b; — meant to shift a then add b.
+ outranks <<, so it is a << (1 + b), a wildly different shift amount. Write (a << 1) + b.if (x = 5) { ... } — meant to test whether x equals 5.
= is assignment, not comparison; it stores 5 into x and yields the truthy value 5, so the branch always runs. This is not a precedence issue — it's the wrong operator. Use ==.printf("%d", a == b < c); — meant "does a equal b < c?".
< (lvl 6) binds tighter than == (lvl 7), so it is a == (b < c), comparing a against a 0/1 result. The author's mental grouping was backwards.int m = a & b | c; — expects a & (b | c).
& (lvl 8) binds tighter than | (lvl 10), so it is (a & b) | c. Bitwise-AND always groups before bitwise-OR; see Bitwise Operators.z = a + b ? x : y; — expects "if a then b, else the ternary".
+ beats ?:, so it is (a + b) ? x : y. The + collapses into the condition; the ternary sees a single value. Parenthesise the intended condition.result = !a == b; — meant "is a false and does that equal b".
Unary
! (lvl 2) beats == (lvl 7), so it is (!a) == b. That is actually usually what you want — but the author feared the wrong grouping, showing they didn't trust the ranking.x = i++ + ++i; — author reasons carefully about precedence to predict the value.
Precedence is irrelevant here:
i is modified twice with no intervening sequence point, so this is undefined behaviour. See Sequence Points and Undefined Behaviour.Why questions
::: answers explain the design reason, not just the rule.
Why does C need associativity in addition to precedence?
Precedence only separates operators of different rank. When two operators tie (like
- and -), a second rule is needed to still produce one unique parse tree. Associativity is that tie-breaker.Why is assignment = made right-associative?
So
a = b = c = 0 reads as "assign 0 rightward through the chain": each assignment returns its stored value, which the next assignment to its left consumes. Left-associativity would try to assign into a value, which is meaningless.Why is the ternary ?: right-associative rather than left?
So chained ternaries form a natural if / else-if / else ladder, with each
: branch able to hold the next whole conditional. See The Ternary Conditional Operator.Why are bitwise & ^ | placed below the comparison operators, given how bug-prone that is?
It is a historical inheritance from B/early C, where
& doubled conceptually near logical use; changing it now would silently break decades of code. The lesson: parenthesise bitwise ops around comparisons, always.Why does precedence not let you predict which function runs first in f() + g()?
Precedence only says the
+ combines the two results; it says nothing about when each call happens. The order of evaluating f() and g() is unspecified — see Expressions and Statements in C.Why is && ranked above ||, mirroring * above +?
Logical AND behaves like a product (a conjunction narrows), OR like a sum (a disjunction widens); the parallel keeps
a || b && c reading as a || (b && c), matching arithmetic intuition. See Logical Operators and Short-circuit Evaluation.Why doesn't a cast like (int)a + b cast the whole sum?
The cast
(type) is a unary operator (lvl 2), tighter than + (lvl 4), so it binds only to a: ((int)a) + b. To cast the sum, write (int)(a + b). See Type Casting and Conversions.Why can't a well-parenthesised expression still be reordered by the compiler?
Parentheses fix grouping (which values combine), but within the allowed grouping the compiler may still evaluate independent sub-expressions in any order, unless a sequence point forces one.
Edge cases
Boundary and degenerate inputs the ranking still governs.
In a && b || c && d, with && higher than ||, what is the grouping?
(a && b) || (c && d) — both AND-groups form first, then OR combines them. No parentheses needed for this idiom, but many add them for readers.Does short-circuit && / || change precedence, or only evaluation?
Only evaluation: precedence still groups
a || (b && c), but short-circuit means b && c may never run if a is true. Grouping and running are separate axes.a, b, c as a full expression — what value does it produce, and what is a and b's role?
The comma yields the last operand
c; a and b are evaluated (for side effects) then discarded. Each comma is a sequence point, so left-to-right order here is guaranteed.~a + 1 (two's-complement negation trick) — does ~ or + bind first?
Unary
~ (lvl 2) binds tighter than + (lvl 4), so it is (~a) + 1. The negation trick works precisely because the ranking groups it this way.*p++ — is it "dereference then increment the value" or "increment the pointer then dereference"?
Postfix
++ (lvl 1) beats unary * (lvl 2), so it is *(p++): read through the old pointer, then advance p. The value read is not incremented.sizeof a + b — does sizeof cover a only or a + b?
sizeof is unary (lvl 2), tighter than +, so it is (sizeof a) + b. To size the whole expression's type you'd need sizeof(a + b).a ? b : c = d — is this even legal, and how does it group?
Assignment (lvl 14) is looser than
?: (lvl 13), so it groups (a ? b : c) = d. In C the ternary result is not an lvalue, so this fails to compile — a case where precedence alone predicts a type error.+ + a (two unary pluses) versus ++a — how does the tokenizer decide?
With a space,
+ +a is unary-plus applied twice (a no-op yielding a's value); without space, ++a is the single pre-increment token. Precedence is downstream of tokenizing — the lexer greedily forms ++ first.Connections
- Expressions and Statements in C
- Bitwise Operators
- Logical Operators and Short-circuit Evaluation
- Sequence Points and Undefined Behaviour
- Type Casting and Conversions
- The Ternary Conditional Operator