Precedence, Associativity, and Type Conversions
How C decides evaluation order on paper, implicit arithmetic conversions, and explicit casts.
Who Computes First
An expression like 2 + 3 * 4 contains a silent decision: which operator gets its operands first. C settles it with two rankings you must be able to apply on paper, because every exam does, and one more subtlety most textbooks skip. This lesson also covers what happens when types mix in one expression, the other half of every "predict the output" question.
Precedence: The Pecking Order
Precedence decides which operator binds tighter. You do not memorize the full table of fifteen levels on day one; you internalize the spine of it:
- Parentheses
()beat everything. - Unary operators:
!,~,++,--, unary-, casts,sizeof. */%+-- Shifts
<<>> - Relational
<<=>>=, then equality==!= - Bitwise
&, then^, then| &&, then||- The conditional
?: - Assignment
=and all compound assignments, nearly the bottom. - The comma operator, dead last.
So 2 + 3 * 4 is 14, and marks >= 40 && attendance >= 75 needs no parentheses because relational outranks &&. But notice line 7: bitwise & and | sit below equality. That makes x & 1 == 0 parse as x & (1 == 0), which is x & 0, always zero, a famous trap. Two working rules follow: rely on precedence for the arithmetic-then-compare shape everyone reads fluently, and parenthesize bitwise operators every time.
Associativity: Ties Among Equals
When operators of the same level share an operand, associativity breaks the tie. The binary arithmetic operators group left to right: 100 - 20 - 5 is (100 - 20) - 5, 75, not 85. Assignment groups right to left: a = b = 0 is a = (b = 0), which is why chained assignment works. The conditional operator and the unary operators also group right to left.
The last line reads left to right: 17 % 12 is 5, then 5 % 5 is 0.
What Precedence Does Not Promise
Here is the subtlety worth a highlighted box: precedence dictates grouping, not order of execution. In f() + g() * h(), the multiplication's result feeds the addition, but C does not say whether f, g, or h is called first; that order is unspecified. For pure arithmetic on variables this is invisible. It becomes visible the moment subexpressions have side effects, which is one more reason the previous lesson's rule, one modification per statement, keeps you safe: obey it and unspecified order has nothing to bite.
Mixing Types: The Conversions
When an expression mixes types, C converts silently, in two stages worth naming.
Integer promotion: char and short values are promoted to int before any arithmetic. Your char grade and char initial compute as ints; this is why %d prints them naturally.
Usual arithmetic conversions: when the operands still differ, the "smaller" converts to the "larger": int meets long, the int widens; anything meets double, it becomes double. So 17 / 5 is integer division, but 17.0 / 5 converts the 5 and divides exactly.
There is one step in that ranking that is not about size at all, and it is the one that draws blood. When a signed and an unsigned type of the same width meet, the signed operand converts to unsigned. Negative values do not survive that trip:
/* WRONG: this comparison is false */
int count = -1;
unsigned int limit = 1u;
count < limit
-1 converted to unsigned int is not a small negative number, it is 4294967295 on a 32-bit int, so the comparison asks whether four billion is less than one and correctly answers no. The compiler does warn, and the warning is worth reading rather than silencing:
warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int' [-Wsign-compare]
8 | printf("-1 < 1u gives %d\n", count < limit);
| ^
The habit that avoids the whole family of bugs: do not mix signedness in one expression. Keep counts and quantities in signed types, reserve unsigned for bit patterns and sizes, and when the two must meet, cast deliberately so the conversion is visible in the source.
Assignment converts too, and this direction can lose data: storing a double into an int truncates the fraction, storing 3.9 gives 3, no rounding, usually with a compiler warning. Every narrowing assignment loses something different, and it is worth seeing the three kinds side by side:
long 4294967297 into an int = 1
double 0.10000000000000001 as float = 0.10000000149011612
(unsigned int) -1 = 4294967295
Three different losses. A long too big for an int keeps only the low-order bits, so 4294967297 arrives as 1: C89 calls the result implementation-defined for a value that does not fit, and GCC discards the high bits. A double moved into a float is rounded to the smaller type's precision rather than truncated, which is why one tenth comes back visibly changed. And a negative value assigned to an unsigned type wraps by the rules of modular arithmetic, giving the largest values of the type rather than an error.
None of these is undefined behaviour, and that is the problem: each is a defined, silent answer that is not the number you meant. Where the value might not fit, check before converting rather than after.
Casts make a conversion explicit: (double)total produces the double version of total. The textbook case is averaging:
Run it with 7 2. The first division happens in int and gives 3; in the second, the cast converts scored first, the conversion rules then pull games up to double, and the division gives 3.50. Placement matters: (double)(scored / games) would divide in int first and convert the already-truncated 3, a mistake exam papers plant annually.
Reading an Expression Like the Compiler
The paper-and-pencil method, worth rehearsing: parenthesize by precedence, break ties with associativity, then evaluate innermost outward, converting types as operands meet. 2 + 10 % 4 * 2.0 becomes 2 + ((10 % 4) * 2.0), the % gives 2 (int), meets 2.0 and widens, multiplies to 4.0, and the final addition widens the 2: result 6.0, a double. Every step is one of this chapter's rules.
Key Takeaways
- Precedence groups operands: unary, then
* / %, then+ -, then comparisons, with assignment near the bottom. - Bitwise
& ^ |rank below==, sox & 1 == 0is the trapx & (1 == 0): parenthesize bitwise always. - Arithmetic associates left to right (
100 - 20 - 5is 75); assignment right to left (a = b = 0). - Precedence fixes grouping, not evaluation order; side effects make the difference visible, so keep them out of compound expressions.
charandshortpromote toint; mixed operands widen to the larger type;doublestored intointtruncates.- Signed meeting unsigned converts the signed operand to unsigned, so
count < limitwithcountnegative is false: never mix signedness in one expression. - Narrowing loses data three different ways, all silent:
longintointkeeps the low bits,doubleintofloatrounds, negative into unsigned wraps. (double)a / bconverts before dividing;(double)(a / b)converts after the damage is done.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Precedence, Associativity, and Type Conversions - Quiz
Test your understanding of the lesson.
Practice Exercises
Two Averages
Read a total and a count, both integers, and print the integer average and the true average. The second needs a cast placed before the division, not after it.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!