Who defines operator precedence and associativity, and how does it relate to order of evaluation?
Categories:
Operator Precedence, Associativity, and Order of Evaluation in C/C++

Explore the critical concepts of operator precedence, associativity, and order of evaluation in C and C++. Understand how these rules dictate expression interpretation and potential pitfalls.
When writing expressions in C or C++, it's crucial to understand how the compiler interprets them. This interpretation is governed by three distinct but related concepts: operator precedence, associativity, and order of evaluation. While often conflated, each plays a specific role in determining the final value of an expression. Misunderstanding these rules can lead to subtle bugs and unexpected behavior in your code.
Operator Precedence: Who Goes First?
Operator precedence defines the order in which operators are applied in an expression. Think of it like the 'order of operations' (PEMDAS/BODMAS) in mathematics. Operators with higher precedence are evaluated before operators with lower precedence. For example, multiplication and division generally have higher precedence than addition and subtraction. This means that in an expression like 2 + 3 * 4
, the multiplication 3 * 4
is performed first, resulting in 2 + 12 = 14
, not (2 + 3) * 4 = 20
.
flowchart TD A[Expression: 2 + 3 * 4] --> B{Check Precedence} B --> C{Multiplication (*)} C --> D[Evaluate 3 * 4 = 12] D --> E{Addition (+)} E --> F[Evaluate 2 + 12 = 14] F --> G[Result: 14]
Flowchart illustrating operator precedence in an expression.
int a = 2;
int b = 3;
int c = 4;
int result1 = a + b * c; // Equivalent to a + (b * c)
// result1 will be 2 + (3 * 4) = 2 + 12 = 14
int result2 = (a + b) * c; // Parentheses override precedence
// result2 will be (2 + 3) * 4 = 5 * 4 = 20
std::cout << "Result 1: " << result1 << std::endl; // Output: 14
std::cout << "Result 2: " << result2 << std::endl; // Output: 20
Demonstration of operator precedence and how parentheses can override it.
Operator Associativity: Which Way to Go?
Associativity comes into play when two or more operators of the same precedence appear in an expression. It defines the direction in which these operators are grouped. Most operators are either left-associative or right-associative.
- Left-associativity: Operators are grouped from left to right. For example, subtraction (
-
) and division (/
) are left-associative. In10 - 5 - 2
, it's interpreted as(10 - 5) - 2 = 5 - 2 = 3
. - Right-associativity: Operators are grouped from right to left. Assignment operators (
=
,+=
, etc.) and unary operators are typically right-associative. For instance,a = b = c
is interpreted asa = (b = c)
. First,c
's value is assigned tob
, and thenb
's new value is assigned toa
.
flowchart LR subgraph Left-Associative (e.g., 10 - 5 - 2) L1[10 - 5 - 2] --> L2[(10 - 5) - 2] L2 --> L3[5 - 2] L3 --> L4[Result: 3] end subgraph Right-Associative (e.g., a = b = c) R1[a = b = c] --> R2[a = (b = c)] R2 --> R3[Assign c to b] R3 --> R4[Assign b's new value to a] R4 --> R5[Result: a, b, c all have original c's value] end
Comparison of left-associative and right-associative operator evaluation.
int x = 10;
int y = 5;
int z = 2;
int result_sub = x - y - z; // (10 - 5) - 2 = 5 - 2 = 3
printf("Subtraction result: %d\n", result_sub); // Output: 3
int a, b, c;
c = 7;
a = b = c; // a = (b = c); b gets 7, then a gets 7
printf("Assignment result: a=%d, b=%d, c=%d\n", a, b, c); // Output: a=7, b=7, c=7
Examples demonstrating left-associativity of subtraction and right-associativity of assignment.
Order of Evaluation: When Things Get Tricky
Order of evaluation refers to the sequence in which the operands of an operator are evaluated, and it's distinct from precedence and associativity. For most operators in C and C++, the order of evaluation of operands is unspecified. This means the compiler is free to evaluate operands in any order it deems efficient. This can lead to undefined behavior if an expression modifies a variable and also uses that variable in a way that depends on the modification's timing within the same expression.
For example, in (i++ + i)
, the compiler might evaluate i++
first, then i
, or vice-versa, or even interleave them. If i
is 5, i++
evaluates to 5 and then i
becomes 6. If i
is then evaluated, it's 6. The result could be 5 + 6 = 11
or 6 + 5 = 11
(if i
is incremented before the second i
is read) or even 5 + 5 = 10
(if the second i
is read before the increment takes effect). This is a classic example of undefined behavior.
++
, --
, or assignment) within the same expression leads to undefined behavior. The compiler is not obligated to produce a consistent result across different compilers, optimization levels, or even different runs of the same program. Always avoid such constructs.int i = 5;
int result = (i++ + i); // UNDEFINED BEHAVIOR!
// Depending on the compiler and optimization, this could print 11, 10, or something else.
// For example, if i++ is evaluated first (value 5, then i becomes 6), then i is evaluated (value 6), result = 5 + 6 = 11.
// If i is evaluated first (value 5), then i++ is evaluated (value 5, then i becomes 6), result = 5 + 5 = 10.
// The C++ standard does not guarantee which order the operands of '+' are evaluated.
std::cout << "Result (undefined behavior): " << result << std::endl;
std::cout << "Final i: " << i << std::endl;
// Correct way to achieve a defined result:
i = 5;
int temp = i++; // temp = 5, i = 6
result = temp + i; // result = 5 + 6 = 11
std::cout << "Defined result: " << result << std::endl;
std::cout << "Final i: " << i << std::endl;
Illustrating undefined behavior due to unspecified order of evaluation and a safe alternative.
Summary and Best Practices
Understanding precedence, associativity, and order of evaluation is fundamental to writing correct and predictable C/C++ code. While the rules for precedence and associativity are well-defined, the unspecified order of evaluation for operands with side effects is a common source of bugs.
Key Takeaways:
- Precedence: Determines which operators are applied first (e.g.,
*
before+
). - Associativity: Determines grouping for operators of the same precedence (e.g.,
a - b - c
is(a - b) - c
). - Order of Evaluation: For most operators, the order in which operands are evaluated is unspecified. Avoid expressions where operand evaluation order matters if side effects are involved.
Best Practices:
- Use Parentheses: When in doubt, use parentheses
()
to explicitly define the order of operations. This improves readability and eliminates ambiguity, even if default precedence/associativity would yield the same result. - Avoid Side Effects in Complex Expressions: Do not modify a variable and use its modified value within the same expression if the order of evaluation is unspecified. Break such expressions into multiple statements.
- Know the Rules: Familiarize yourself with the precedence and associativity tables for C/C++ operators, but prioritize clarity with parentheses.