Introduction
An expression in C computes a value and may also change the state of the program, for example by assigning to a variable or printing. The value depends on how the expression is grouped (precedence and associativity); the state changes depend on when each side effect happens (sequencing). These are two different questions, and confusing them is the root of most wrong answers to C output questions.
This note covers the operators, the precedence and associativity rules that group them, the operators that guarantee an order of evaluation (&&, ||, ?: and the comma operator), and the rule that makes expressions such as i = i++ undefined.
Conventions: C11, hosted, LP64 data model, as fixed in C Program Structure and Data Types. Bitwise operators have their own note, Bitwise Operators.
Prerequisites
- C Program Structure and Data Types: integer and floating types, integer division.
Core concepts
Operands, operators and side effects
- An lvalue is an expression that designates an object, such as a variable
x, an array elementa[i]or*p. Only a modifiable lvalue can appear on the left of=or as the operand of++and--.x + 1 = 5and5++do not compile. - A side effect is a change of state: modifying an object, modifying a file, or accessing a
volatileobject. Assignment,++,--and calls such asprintfhave side effects. - The value of an assignment expression is the value stored in the left operand after the assignment, with the left operand's type. So
a = b = cstorescintoband then that value intoa.
Arithmetic, relational and logical operators
- Arithmetic:
+ - * / %; unary+and-. Integer/truncates toward zero and%takes the sign of the dividend. - Relational and equality:
< <= > >= == !=. Each yields anintthat is 1 if true and 0 if false. - Logical:
!,&&,||. Any nonzero operand counts as true. The result is always 0 or 1:!5is 0,!0is 1, and-!0is . - Increment and decrement:
++xincrementsxand yields the new value;x++yields the old value and incrementsxas a side effect. The same holds for--. - Assignment:
=and the compound forms+= -= *= /= %= <<= >>= &= ^= |=.x op= emeansx = x op (e), except thatxis evaluated only once. Sox *= y + 1multiplies by(y + 1).
Precedence and associativity
Precedence decides which operator takes its operands first when different operators meet; associativity decides grouping when operators of the same level meet. The C standard does not print a table; the table below is derived from the grammar in C11 Section 6.5, from highest to lowest.
| Level | Operators | Associativity |
|---|---|---|
| 1 | () call, [], ., ->, postfix ++ -- | left to right |
| 2 | prefix ++ --, unary + -, !, ~, cast (type), * (indirection), & (address), sizeof, _Alignof | right to left |
| 3 | * / % | left to right |
| 4 | + - | left to right |
| 5 | << >> | left to right |
| 6 | < <= > >= | left to right |
| 7 | == != | left to right |
| 8 | & (bitwise AND) | left to right |
| 9 | ^ | left to right |
| 10 | | | left to right |
| 11 | && | left to right |
| 12 | || | left to right |
| 13 | ?: | right to left |
| 14 | = and all compound assignments | right to left |
| 15 | , (comma operator) | left to right |

Groupings that are easy to misread:
1 << 2 + 1is1 << (2 + 1), which is 8: addition binds tighter than shift.x & 1 == 0isx & (1 == 0), which isx & 0, always 0. Write(x & 1) == 0.5 > 3 > 1is(5 > 3) > 1, which is1 > 1, which is 0. C has no chained comparison. Likewise1 < 2 < 3is 1 for the wrong reason, and3 > 2 > 1is 0.10 - 4 - 3is(10 - 4) - 3, which is 3 (left associativity), whilea = b = cisa = (b = c)(right associativity).*p++is*(p++): postfix++binds tighter than unary*. It yields the value at the oldpand advancesp.(*p)++increments the pointee instead. See Pointers.2 + 3 * 4 % 5is2 + ((3 * 4) % 5), which is :*and%share a level and group left to right.
Grouping is not order of evaluation
Precedence tells you that f() + g() * h() means f() + (g() * h()). It does not tell you which of f, g and h is called first. For most operators the order in which operands are evaluated is unspecified: the compiler may choose any order, and need not choose the same order each time. A program whose output depends on that order is not wrong, but its output is not fixed by the language. If f prints f and g prints g, then f() + g() may print fg or gf; gcc on x86-64 happens to print fg, which proves nothing about other compilers.
The order of evaluation of function arguments is also unspecified: in printf("%d %d", a(), b()), either call may happen first.
Sequencing and the undefined-behaviour rule
C11 describes ordering with the relation sequenced before (C99 used "sequence points"). The key facts:
- The full expressions of consecutive statements are sequenced: everything in one statement finishes before the next statement starts.
&&,||,?:(between the condition and the chosen branch) and the comma operator sequence their left operand before their right operand.- All arguments and the function designator are evaluated before the called function's body starts. The argument evaluations are unsequenced relative to one another, so
g(i, i++)is undefined. Two calls inside the arguments, as ing(a(), b()), are indeterminately sequenced: one runs completely before the other, in an unspecified order. - The side effect of
=,++and--on the object is sequenced after the value computation of their operands, but not necessarily before other parts of the enclosing expression.
The rule (C11 6.5 paragraph 2): if a side effect on a scalar object is unsequenced relative to either another side effect on the same object or a value computation that uses the value of the same object, the behaviour is undefined. Undefined means the standard imposes no requirement at all; the program may print anything, crash, or appear to work.
| Expression | Status | Reason |
|---|---|---|
i = i++; | undefined | two unsequenced modifications of i (the store of = and the increment) |
k = i++ + ++i; | undefined | two unsequenced modifications of i |
a[i] = i++; | undefined | modification of i unsequenced with the read of i in a[i] |
printf("%d %d", i, i++); | undefined | arguments unsequenced: a read and a modification of i |
i = i + 1; | well defined | one modification; the read is part of computing the stored value |
i++ && i++ | well defined | && sequences the left operand before the right |
j = i++, i; (comma operator) | well defined | comma sequences i++ before the read of i |
f() + g() with side effects in different objects | unspecified order | either call may come first; no undefined behaviour |
gcc reports the first four with -Wall ("operation on 'i' may be undefined"). A question that asks for the output of i = i++ + ++i has no correct numeric answer under the C standard; the defensible answer is "undefined behaviour". See Undefined, Unspecified and Implementation-Defined Behaviour.
Short-circuit evaluation
e1 && e2 evaluates e1 first; if it is 0, the result is 0 and e2 is not evaluated at all. e1 || e2 evaluates e1; if it is nonzero, the result is 1 and e2 is skipped. Side effects in the skipped operand never happen.
Short-circuit guards avoid an access only when their preconditions are valid. In p != NULL && p->key == k, p must already be null or a valid pointer to a live object of the right type, not dangling. In 0 <= i && i < n && a[i] != x, require a valid array with n no greater than its actual length; an unsigned i already satisfies the lower bound. Only then does the left-to-right short circuit establish safe indexing.
Conditional and comma operators
c ? e1 : e2evaluatesc; then exactly one ofe1,e2. It groups right to left, soa ? b : c ? d : eisa ? b : (c ? d : e).- The comma operator
e1, e2evaluatese1, discards its value, then evaluatese2, whose value and type are the result.x = (1, 2, 3)setsxto 3. Without parentheses,x = 1, 2, 3;setsxto 1, because=binds tighter than,. The commas that separate function arguments or declarators (int a = 1, b = 2;) are punctuation, not the comma operator.
How to evaluate an expression by hand
- Parse. Insert parentheses according to precedence and associativity.
- Check legality. First account for short-circuit/conditional branches and unevaluated operands. Among the operands actually evaluated, unsequenced conflicting modifications, or a modification and value computation of the same scalar object, trigger undefined behaviour; only then stop.
- Evaluate inside out. For
&&,||and?:, decide which operands are evaluated at all before computing anything inside them. - Apply side effects and record the final value of every variable.
- Types. Track the type at every step: integer division, promotions and conversions change results (Type Conversions).
Worked examples
All outputs below are for C11 with gcc on x86-64 Linux (LP64).
Example 1 (short-circuit).
int i = 0, j = 0, k;
k = i++ && j++; /* i++ yields 0, so j++ is skipped */
printf("%d %d %d\n", i, j, k); /* 1 0 0 */
i = 0; j = 0;
k = i++ || j++; /* left yields 0, so j++ runs; it yields 0 */
printf("%d %d %d\n", i, j, k); /* 1 1 0 */
i = 1; j = 5;
k = i++ || j++; /* left yields 1: j++ skipped */
printf("%d %d %d\n", i, j, k); /* 2 5 1 */Example 2 (mixed logical operators). With i = -1, j = 0, k = 0, evaluate m = ++i && ++j || ++k. Parse: (++i && ++j) || ++k. ++i makes i 0, so && is 0 without evaluating ++j. Then || evaluates ++k, which is 1, so m is 1. Final: i = 0, j = 0, k = 1, m = 1.
Example 3 (assignment values). int a, b, c = 3; a = b = c; int d = (a += 2) * 2; leaves a = 5, b = 3, c = 3, d = 10.
Example 4 (increment, comma, conditional).
int x = (1, 2, 3); /* 3 */
int a = 5, b = a++, c = ++a; /* b = 5, then a = 6, c = 7, a = 7 */
int y = 0;
int t = y ? 10 : y == 0 ? 20 : 30; /* 20 */
int i = 2; int r = i++ * 3; /* r = 6, i = 3 */The declaration int a = 5, b = a++, c = ++a; is well defined: each declarator's initialiser is a full expression, sequenced before the next.
Example 5 (compound assignment). int x = 3, y = 4; x *= y + 1; gives 15, not 13. int z = 17; z %= 5; z <<= 2; gives 8.
Important properties and observations
- Relational, equality and logical operators yield
int0 or 1. &&and||short-circuit and sequence their operands;&and|do neither.- Precedence decides grouping; it never decides evaluation order.
- Unsequenced modification and access of the same scalar is undefined behaviour, not "compiler-dependent output".
- Unary operators,
?:and assignment group right to left; everything else groups left to right. - Bitwise
&,^,|sit below==and!=; shifts sit below+and-. Parenthesise. - The comma operator has the lowest precedence of all.
GATE examination perspective
- Short-circuit traces are the most reliable kind of trace: count which increments actually run.
- Precedence traps with
&versus==, shift versus+, and chained relational operators. *p++versus(*p)++versus++*pin pointer questions.- Undefined expressions. Choose "undefined behaviour". If only "compiler dependent" is offered, choose it; the precise term is undefined behaviour. If neither option is offered, state the assumption explicitly in your working.
- Value of an assignment inside a condition.
if (x = 0)assigns 0 and is false;if (x = 5)is true. This is legal C (gcc warns); the assignment is the condition's value.
Summary
Sources
- ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Section 5.1.2.3 (program execution, sequencing), 6.5 (expressions, paragraph 2 on unsequenced side effects), 6.5.2.2 (function calls), 6.5.2.4 and 6.5.3.1 (increment and decrement), 6.5.13 to 6.5.17 (logical, conditional, assignment and comma operators).
- B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Chapter 2 (types, operators and expressions), including the precedence table in Section 2.12.