C / OPERATORS AND EXPRESSIONS
Operator precedence and the parentheses habit
Group any C expression the way the compiler does, using precedence then associativity, and place parentheses where C's table surprises you.
What you will learn
- Group an unparenthesised expression by precedence first, then by associativity.
- Recall that &, ^ and | bind looser than == and !=, so mask tests need parentheses.
- Parenthesise shifts mixed with + or -, and any ?: used as an operand.
- Treat grouping as a parse-time rule, not a promise about evaluation order.
Understanding Operator precedence and the parentheses habit
When you write a + b * c, C has to decide which operator gets to keep b. Precedence answers that: * sits at a higher level than +, so b binds to the multiplication and the expression parses as a + (b * c). When two operators sit at the same level, associativity settles it: 10 - 4 - 3 is (10 - 4) - 3 because the additive level is left-associative, while x = y = 0 is x = (y = 0) because assignment is right-associative. The outcome of both rules is a tree, and parentheses are simply a way to draw that tree yourself instead of letting the table draw it for you.
The trouble is the size and the shape of that table. C has roughly fifteen levels, and a few of them sit where nobody expects: bitwise &, ^ and | all bind looser than == and !=, which is why flags & MASK == MASK quietly means flags & (MASK == MASK). That layout is a historical accident, not a design choice you can reason your way to. Before && existed, & was the operator you used inside conditions, so it was placed below the relational operators; when && was added the old levels stayed put so existing code would keep compiling. The shift operators are the other frequent trap, because they bind looser than + and -, so 1 << n + 1 shifts by n + 1.
The habit that survives contact with real code is to memorise only the short chain you use constantly, unary, then * / %, then + -, then relational, then &&, then ||, then assignment, and to parenthesise the moment you mix in anything outside it. Bitwise operators next to comparisons, shifts next to arithmetic, ?: used as an operand, and a cast next to a binary operator all earn explicit parentheses even when you are confident about the level, because the next reader will not be. Compiling with -Wall helps: gcc and clang implement -Wparentheses for precisely these combinations, and adding the parentheses they suggest costs nothing at runtime.
<stdio.h>
int main(void)
{
int x = 6; /* bits: 0110 */
int n = 2;
printf("%d\n", x & 1 == 0); /* parses as x & (1 == 0) */
printf("%d\n", (x & 1) == 0); /* the intended test */
printf("%d\n", 1 << n + 1); /* parses as 1 << (n + 1) */
printf("%d\n", (1 << n) + 1); /* the intended value */
printf("%d\n", 10 - 4 - 3); /* left associative: (10 - 4) - 3 */
printf("%d\n", 10 - (4 - 3));
return 0;
}
Precedence and associativity only decide how an expression is grouped into a tree, and C's table has enough oddities that explicit parentheses beat recall.
Worked examples
Where the parentheses go around a pointer
Postfix ++ binds tighter than unary *, so the parentheses decide whether you increment the pointer or the int it points at.
<stdio.h>
int main(void)
{
int a[3] = {10, 20, 30};
int *p = a;
int v;
v = *p++;
printf("v=%d *p=%d\n", v, *p);
p = a;
(*p)++;
printf("a[0]=%d p==a is %d\n", a[0], p == a);
return 0;
}
Example explained
Line 1v = *p++ groups as v = *(p++), because postfix ++ is one level above unary *; the dereference uses the old pointer, so v gets a[0].
Line 2p was advanced by that same statement, which is why *p prints 20 on the next line.
Line 3(*p)++ needs its parentheses to reach the int: it raises a[0] from 10 to 11 and leaves p alone.
Line 4p == a printing 1 confirms the pointer itself was untouched; without the parentheses you would have moved p instead.
Casts bind tight, ?: binds loose
A cast grabs only the operand next to it, while a conditional expression sits below almost everything.
<stdio.h>
int main(void)
{
int total = 7, count = 2;
printf("%.2f\n", (double)total / count);
printf("%.2f\n", (double)(total / count));
printf("%d\n", 1 + count > 2 ? 10 : 20);
printf("%d\n", 1 + (count > 2 ? 10 : 20));
return 0;
}
Example explained
Line 1A cast is a unary operator, so (double)total / count converts total only and then divides a double by an int, giving 3.50.
Line 2Moving the parentheses to enclose the whole quotient makes the int division happen first, and 3 is widened afterwards to 3.00.
Line 3?: sits below + and >, so 1 + count > 2 ? 10 : 20 parses as ((1 + count) > 2) ? 10 : 20, which is true and yields 10.
Line 4Parenthesising the conditional turns it into an operand of +: 2 > 2 is false, so 20 + 1 prints 21.
Important notes
sizeof is a unary operator, so sizeof x + 1 means (sizeof x) + 1; the parentheses in sizeof(int) belong to the type name, not to the operator.
Precedence never guarantees evaluation order: (a + b) * c promises only that the sum is one operand of the multiply, not that a is read before c.
Common mistakes
Writing if (flags & MASK == MASK) to test several bits at once: MASK == MASK is 1, so the condition collapses to flags & 1 and only ever looks at bit 0.
Writing 1 << n + 1 when (1 << n) + 1 was intended: for n = 2 that produces 8 instead of 5, because the shift count becomes 3.
Believing parentheses force an order in time, so that in (f() + g()) * h() the calls must happen left to right; parentheses only group operands, they say nothing about when each one is evaluated.
Try it yourself
Change, predict, then run
In a browser editor, print 2 + 3 * 4 % 5, then ~1 & 3, then 8 >> 1 + 1, writing down your predicted value for each before you run it. Then rewrite all three fully parenthesised and confirm the printed numbers are unchanged.
Open the C workspaceCheck your understanding
Given int f = 5;, why does if (f & 3 == 3) fail as a test for "both low bits of f are set"?
- == binds tighter than &, so the condition is f & (3 == 3), which is f & 1 and only inspects bit 0
- & and == are on the same precedence level and associate right to left, so the comparison is grouped first
- & yields an int rather than a truth value, so it needs a cast before it can be used in an if condition
- 3 == 3 is a constant expression, so the compiler folds the whole condition away and the if never runs
Show answer
== sits several levels above &, so the expression parses as f & (3 == 3), which is f & 1; that is true for any odd f and false for any even f, and bit 1 is never examined. Option 1 predicts the same grouping but for the wrong reason, and that reason would mislead you elsewhere: the two operators are on different levels, and both are left-associative, so associativity plays no part here at all.