C Part I · Module 3 · Expressions and Operators
Operator Precedence and Associativity
Every expression in this module so far has had one operator, or operators so obviously separated that grouping never came up. Real expressions mix several, and then a question arises that the writing order alone does not answer: which operands belong to which operator? C settles it with two rules — precedence between different operators, associativity between operators of equal standing — and this chapter builds both from examples rather than from a table. It also draws a line that the next chapter depends on completely, because almost everyone arrives believing these rules say more than they do: precedence decides how an expression groups, and it says nothing whatsoever about the order in which the pieces are evaluated.
Intermediate12 min readCPart IFundamentalsExpressions
Part I · Module 3 · Chapter 3.10 · Operator Precedence and Associativity
1. One Expression, Two Possible Readings
2 + 3 * 4Two readings are available, and they disagree:
(2 + 3) * 4 = 20
2 + (3 * 4) = 14C has to pick one, and it does not pick by reading left to right. The rule is precedence: when different operators meet, the one that binds more tightly takes its operands first.
#include <stdio.h>
int main(void)
{
printf("2 + 3 * 4 = %d\n", 2 + 3 * 4);
printf("(2 + 3) * 4 = %d\n", (2 + 3) * 4);
return 0;
}Output:
2 + 3 * 4 = 14
(2 + 3) * 4 = 2014, so * bound more tightly and C read it as 2 + (3 * 4). That matches ordinary arithmetic, which is why nobody is surprised here — and is exactly why the surprises later in this chapter catch people.
Note the word grouping. Precedence answers the question which operands belong to which operator. It is a question about the shape of the expression, settled before the program runs.
2. Associativity: When the Operators Are Equal
Precedence only helps when the operators differ. When two operators of the same precedence compete for an operand, associativity settles it.
10 - 4 - 3Both operators are -, so precedence has nothing to say. The two readings differ:
(10 - 4) - 3 = 3 left-associative
10 - (4 - 3) = 9 right-associativeSubtraction in C is left-associative, so the answer is 3 — verified by running it. Most binary operators work this way, which is why left-to-right reading usually feels right.
Assignment is the notable exception. Chapter 3.4 used b = a = 7 and said assignments group from the right; this is the rule behind it:
int a, b;
a = b = 5;Both end up holding 5, because this is a = (b = 5): the inner assignment stores 5 in b and produces 5, and the outer stores that in a. Read left-associatively it would be (a = b) = 5, which is not even valid — Chapter 3.4 explained that the left side of an assignment must name somewhere a value can go, and (a = b) does not.
So associativity is not arbitrary: for assignment, only the rightward reading produces a meaningful expression.
3. The Trap Worth Knowing
Here is a precedence surprise that costs real time, and it uses exactly the operators Chapters 3.5 and 3.8 gave you:
#include <stdio.h>
int main(void)
{
unsigned int value = 0x0Au;
unsigned int mask = 0x02u;
unsigned int expected = 0x02u;
printf("value & mask == expected -> %u\n", value & mask == expected);
printf("(value & mask) == expected -> %u\n", (value & mask) == expected);
printf("mask == expected -> %d\n", mask == expected);
return 0;
}Output:
value & mask == expected -> 0
(value & mask) == expected -> 1
mask == expected -> 1The first line looks like mask off the bits, then check the result. It is not what C does.
== binds more tightly than &. So C reads it as:
written: value & mask == expected
grouped: value & (mask == expected)Follow it through. mask == expected is 0x02 == 0x02, which the third line confirms is 1. The expression then becomes 0x0A & 1, and in bits that is 1010 & 0001, which shares no set bit — so the answer is 0.
Nothing malfunctioned. The operands were grouped by C's rules, and C's rules do not match the way the line reads in English.
Your compiler will flag it if you ask:
warning: & has lower precedence than ==; == will be evaluated first [-Wparentheses]which is an unusually helpful diagnostic — it names the exact misreading. As always, wording varies between compilers; the category is what to recognise.
The fix is the second line: parentheses saying what you meant. And this is why Chapter 3.8 wrote (flags & bit1) != 0 with parentheses that looked redundant. They were not.
4. Enough of the Table to Reason With
A complete precedence table has fifteen or so levels and memorising it is not a good use of your attention. Here are the operators you have actually met, from tightest to loosest:
| Binding | Operators | Associativity |
|---|---|---|
| tightest | ++ -- (postfix) | left |
! ~ - (unary), (type) cast | right | |
* / % | left | |
+ - | left | |
<< >> | left | |
< <= > >= | left | |
== != | left | |
& | left | |
^ | left | |
| | left | |
&& | left | |
|| | left | |
| loosest | = += -= *= /= %= | right |
Four observations are worth more than the table itself:
Arithmetic binds tighter than comparison, which binds tighter than logic. So a + 1 < b && c > 0 groups as ((a + 1) < b) && (c > 0) — the usual reading, and the reason simple conditions need no parentheses.
The bitwise operators sit below the comparisons. That is §3's trap, and it is the one genuine surprise in the table. &, ^ and | are looser than ==, which is almost never what the author intends.
Shifts sit between arithmetic and comparison, which means a << 1 + 2 is a << (1 + 2) — a shift by three, not a shift by one followed by adding two. Another good reason for parentheses.
Assignment is loosest and groups rightward. That combination is what makes a = b = 5 work and what keeps x = a + b from needing parentheses.
5. Parentheses Change Grouping — and Only Grouping
Parentheses override the default grouping. (2 + 3) * 4 groups the addition first, and that is a guarantee.
But be precise about what you have bought:
Parentheses control grouping. They do not impose an order of evaluation.
In (a) + (b) the parentheses say that a and b are the operands of +. They do not say that a is evaluated before b. For operands that merely read values that distinction is invisible; for operands with side effects it is exactly where Chapter 3.11's problems live.
Keep the two apart and the next chapter will be straightforward. Blur them and nothing in it will make sense.
6. What You Should Be Able to Say Now
- Precedence decides which operands belong to which operator when different operators meet.
- Associativity decides it when operators of the same precedence compete.
2 + 3 * 4is2 + (3 * 4)because*binds more tightly.- Most binary operators are left-associative;
10 - 4 - 3is(10 - 4) - 3= 3. - Assignment is right-associative, which is the only reading under which
a = b = 5means anything. ==binds more tightly than&, sovalue & mask == expectedgroups asvalue & (mask == expected).- Bitwise operators sitting below comparisons is the table's one real surprise.
- Shifts sit between arithmetic and comparison:
a << 1 + 2shifts by three. - Parentheses change grouping and nothing else — in particular, not evaluation order.
- If a reader would need the table, add parentheses.
7. Exercise
Exercise 1 — parenthesise. Rewrite each with parentheses showing C's grouping, without changing its meaning:
(a) 1 + 2 * 3 - 4
(b) a < b == c < d
(c) x & y | z
(d) p = q = r
(e) v << 2 + 1
Exercise 2 — predict, then run. Give the value of each and say which rule decided it: 20 / 4 / 5, 2 + 3 * 4 - 1, 100 - 10 - 10.
Exercise 3 — explain the trap. Using §3, explain in three sentences why value & mask == expected gave 0 when value was 0x0A, mask was 0x02 and expected was 0x02. Show the grouping and the intermediate value.
Exercise 4 — fix the real bug. This is meant to test whether any of the masked bits are set and always reports the same thing. Say what it actually computes and give the corrected line:
unsigned int status = 0x30u;
unsigned int ready_bits = 0x10u;
int is_ready = status & ready_bits != 0;Exercise 5 — separate the two questions. For (a + b) * (c + d), say what the parentheses guarantee and what they do not. Answer in two sentences, one for each.
Exercise 6 — decide on parentheses. For each, say whether you would add parentheses and why: x * 2 + 1; flags & MASK; a && b || c; value >> 2 & 0x0Fu.
8. Summary
- Precedence groups different operators; associativity groups equal ones. Both are settled before the program runs.
2 + 3 * 4is2 + (3 * 4). Most binary operators group leftward.- Assignment groups rightward, which is what makes
a = b = 5meaningful. ==binds tighter than&— the table's one genuine surprise, and the source of a real class of bug.- Shifts sit between arithmetic and comparison, so
a << 1 + 2shifts by three. - Parentheses change grouping only. They do not impose an evaluation order, and believing otherwise is what the next chapter exists to correct.
- Write the parentheses whenever a reader would otherwise need the table.
You now know exactly how any expression in this module groups — which operands belong to which operator, in every case, with no ambiguity. That feels like it should be the whole story, and it is not. Knowing the shape of an expression tells you nothing about the order in which its pieces are worked out, and C promises far less about that order than almost anyone assumes.
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.
