Operator Precedence and Associativity in C: Guide
Operator Precedence and Associativity in C: A Comprehensive Tutorial
Operator precedence and associativity are fundamental rules that determine the order in which operators are evaluated in C expressions. Understanding these rules is essential for writing correct and predictable code, avoiding subtle bugs, and interpreting expressions as the compiler does.
This tutorial provides an in-depth exploration of operator precedence and associativity in C: their definitions, the complete precedence table, practical examples, common pitfalls, and best practices.
Conceptual Introduction & Fundamentals
What is Operator Precedence?
Operator precedence determines which operator is evaluated first when an expression contains multiple operators. Operators with higher precedence are evaluated before operators with lower precedence.
Example:
c
int result = 10 + 20 * 30;
In this expression, * has higher precedence than +, so multiplication is performed first:
text
10 + (20 * 30) = 10 + 600 = 610
Not:
text
(10 + 20) * 30 = 30 * 30 = 900
What is Operator Associativity?
Associativity determines the order of evaluation when two or more operators have the same precedence. Associativity can be:
- Left-to-Right (L→R): Operations are evaluated from left to right.
- Right-to-Left (R→L): Operations are evaluated from right to left.
Example (Left-to-Right):
c
int result = 100 / 5 % 2;
Both / and % have the same precedence. They are left-associative:
text
(100 / 5) % 2 = 20 % 2 = 0
Example (Right-to-Left):
c
int a, b, c; a = b = c = 10;
Assignment = is right-associative:
text
a = (b = (c = 10)) = a = (b = 10) = a = 10
Why Precedence and Associativity Matter
- Correctness: Misunderstanding precedence leads to incorrect results.
- Predictability: Knowing the rules ensures your code behaves as intended.
- Readability: Using parentheses to clarify intent (even when not strictly needed) makes code easier to understand.
- Portability: Precedence and associativity are standardized in C, so code behaves consistently across compilers.
Evaluation Order vs. Precedence
Precedence and associativity determine the grouping of operands with operators. They do not determine the order of evaluation of operands. For example:
c
int result = f1() + f2() * f3();
Precedence says f2() * f3() is grouped together, but the order in which f1(), f2(), and f3() are called is unspecified.
Syntax & Core Mechanics
The Complete Operator Precedence Table
The following table lists C operators from highest to lowest precedence, along with their associativity:
| Precedence | Operator | Description | Associativity |
|---|---|---|---|
| 1 (highest) | () | Function call | Left-to-Right |
[] | Array subscript | ||
. | Structure member access | ||
-> | Structure pointer member access | ||
++, -- | Postfix increment/decrement | ||
| 2 | ++, -- | Prefix increment/decrement | Right-to-Left |
+, - | Unary plus/minus | ||
!, ~ | Logical NOT, Bitwise NOT | ||
(type) | Cast operator | ||
* | Dereference | ||
& | Address-of | ||
sizeof | Size in bytes | ||
| 3 | *, /, % | Multiplication, division, modulus | Left-to-Right |
| 4 | +, - | Addition, subtraction | Left-to-Right |
| 5 | <<, >> | Bitwise shift left/right | Left-to-Right |
| 6 | <, <=, >, >= | Relational operators | Left-to-Right |
| 7 | ==, != | Equality operators | Left-to-Right |
| 8 | & | Bitwise AND | Left-to-Right |
| 9 | ^ | Bitwise XOR | Left-to-Right |
| 10 | | | Bitwise OR | Left-to-Right |
| 11 | && | Logical AND | Left-to-Right |
| 12 | || | Logical OR | Left-to-Right |
| 13 | ?: | Ternary conditional | Right-to-Left |
| 14 | = | Assignment | Right-to-Left |
+=, -=, *=, /=, %= | Compound assignment | ||
<<=, >>=, &=, ^=, |= | Compound assignment | ||
| 15 (lowest) | , | Comma (expression separator) | Left-to-Right |
Memory Trick: PUMA’S REBL TAC
An easy way to remember the precedence order:
P = Postfix
U = Unary
M = Multiplicative
A = Additive
S = Shift
R = Relational
E = Equality
B = Bitwise
L = Logical
T = Ternary
A = Assignment
C = Comma
Detailed Examples
Example 1: Multiplicative vs. Additive
c
#include <stdio.h> int main(void) { int a = 6, b = 3, c = 4; int res; // * and / have higher precedence than + // * and / are left-associative res = a + b * c / 2; // Evaluation: a + ((b * c) / 2) = 6 + ((3 * 4) / 2) = 6 + 6 = 12 printf("%d\n", res); return 0; } Expected output:
text
12
Example 2: Left-to-Right Associativity
c
#include <stdio.h> int main(void) { // / and % have same precedence, left-to-right int result = 100 / 5 % 2; // (100 / 5) % 2 = 20 % 2 = 0 printf("%d\n", result); return 0; } Expected output:
text
0
Example 3: Right-to-Left Associativity
c
#include <stdio.h> int main(void) { int a, b, c; // Assignment is right-associative a = b = c = 10; printf("a = %d, b = %d, c = %d\n", a, b, c); return 0; } Expected output:
text
a = 10, b = 10, c = 10
Example 4: Using Parentheses to Change Order
c
#include <stdio.h> int main(void) { int result1 = 100 + 200 / 10 - 3 * 10; // 100 + (200/10) - (3*10) = 100 + 20 - 30 = 90 int result2 = (100 + 200) / 10 - 3 * 10; // (300) / 10 - 30 = 30 - 30 = 0 printf("result1 = %d\n", result1); printf("result2 = %d\n", result2); return 0; } Expected output:
text
result1 = 90 result2 = 0
Example 5: Postfix vs. Prefix Increment
c
#include <stdio.h> int main(void) { int a = 5; int b = 5; // Postfix ++ has higher precedence than prefix ++ int result1 = a++ + ++a; // Undefined behavior (side effects on same variable) // Avoid such expressions! // Better: separate statements int c = 5; int d = c++; // d = 5, c = 6 int e = ++c; // e = 7, c = 7 printf("d = %d, e = %d, c = %d\n", d, e, c); return 0; } Expected output:
text
d = 5, e = 7, c = 7
Full Practical Code Example with Explanation
This program demonstrates operator precedence and associativity in a realistic scenario: a mathematical expression evaluator that shows step-by-step evaluation.
Program: Expression Evaluation Demonstrator
c
/** * precedence_demo.c * Demonstrates operator precedence and associativity in C. * Evaluates various expressions and shows the order of operations. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_LINE_LEN 100 /* Function prototypes */ void demonstratePrecedence(void); void demonstrateAssociativity(void); void demonstrateParentheses(void); void demonstrateCommonMistakes(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; while (1) { printf("\n=== Operator Precedence and Associativity ===\n"); printf("1. Demonstrate Precedence\n"); printf("2. Demonstrate Associativity\n"); printf("3. Demonstrate Parentheses Effect\n"); printf("4. Demonstrate Common Mistakes\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstratePrecedence(); break; case 2: demonstrateAssociativity(); break; case 3: demonstrateParentheses(); break; case 4: demonstrateCommonMistakes(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Demonstrates precedence of different operator categories */ void demonstratePrecedence(void) { printf("\n=== Operator Precedence ===\n"); /* Multiplicative vs. Additive */ int a = 10 + 20 * 30; printf("10 + 20 * 30 = %d\n", a); printf(" (20 * 30 evaluated first, then + 10)\n"); /* Shift vs. Relational */ int b = 5 << 2 > 10; printf("5 << 2 > 10 = %d\n", b); printf(" ((5 << 2) > 10) = (20 > 10) = 1\n"); /* Relational vs. Equality */ int c = 5 > 3 == 1; printf("5 > 3 == 1 = %d\n", c); printf(" ((5 > 3) == 1) = (1 == 1) = 1\n"); /* Bitwise vs. Logical */ int d = 5 & 3 == 3; printf("5 & 3 == 3 = %d\n", d); printf(" (5 & (3 == 3)) = (5 & 1) = 1\n"); printf(" Note: == has higher precedence than &\n"); int e = (5 & 3) == 3; printf("(5 & 3) == 3 = %d\n", e); printf(" ((5 & 3) == 3) = (1 == 3) = 0\n"); /* Logical AND vs. Logical OR */ int f = 1 || 0 && 0; printf("1 || 0 && 0 = %d\n", f); printf(" (1 || (0 && 0)) = (1 || 0) = 1\n"); } /* Demonstrates associativity of operators */ void demonstrateAssociativity(void) { printf("\n=== Operator Associativity ===\n"); /* Left-to-right: / and % */ int a = 100 / 5 % 2; printf("100 / 5 %% 2 = %d\n", a); printf(" ((100 / 5) %% 2) = (20 %% 2) = 0\n"); /* Left-to-right: - and + */ int b = 10 - 3 + 2; printf("10 - 3 + 2 = %d\n", b); printf(" ((10 - 3) + 2) = (7 + 2) = 9\n"); /* Right-to-left: assignment */ int x, y, z; x = y = z = 42; printf("x = y = z = 42 => x=%d, y=%d, z=%d\n", x, y, z); printf(" (x = (y = (z = 42)))\n"); /* Right-to-left: ternary */ int c = 5 > 3 ? 10 : 20 > 10 ? 30 : 40; printf("5 > 3 ? 10 : 20 > 10 ? 30 : 40 = %d\n", c); printf(" (5 > 3 ? 10 : (20 > 10 ? 30 : 40)) = 10\n"); /* Right-to-left: unary */ int d = 5; int e = -~d; // ~d = -6, then unary minus = 6 printf("-~5 = %d\n", e); printf(" (-(~5)) = (-(-6)) = 6\n"); } /* Demonstrates how parentheses change evaluation order */ void demonstrateParentheses(void) { printf("\n=== Parentheses Effect ===\n"); int a = 100 + 200 / 10 - 3 * 10; printf("100 + 200 / 10 - 3 * 10 = %d\n", a); printf(" (100 + (200/10) - (3*10)) = 90\n"); int b = (100 + 200) / 10 - 3 * 10; printf("(100 + 200) / 10 - 3 * 10 = %d\n", b); printf(" ((300) / 10 - 30) = 0\n"); int c = 100 + 200 / (10 - 3) * 10; printf("100 + 200 / (10 - 3) * 10 = %d\n", c); printf(" (100 + (200 / 7) * 10) = 100 + 28*10 = 380\n"); int d = (100 + 200) / (10 - 3) * 10; printf("(100 + 200) / (10 - 3) * 10 = %d\n", d); printf(" ((300) / (7) * 10) = 42*10 = 420\n"); } /* Demonstrates common mistakes due to misunderstanding precedence */ void demonstrateCommonMistakes(void) { printf("\n=== Common Mistakes ===\n"); /* Mistake 1: No chaining comparison */ int a = 10, b = 20, c = 30; if (c > b > a) { printf("c > b > a is TRUE\n"); } else { printf("c > b > a is FALSE\n"); } printf(" ((c > b) > a) = (1 > 10) = 0\n"); /* Mistake 2: Assignment vs. comparison */ int x = 5; if (x = 10) { printf("x = 10 is TRUE (but assignment happened!)\n"); } printf(" Use x == 10 for comparison\n"); /* Mistake 3: Bitwise vs. logical */ int d = 1, e = 2; if (d & e) { printf("d & e is non-zero (bitwise AND)\n"); } if (d && e) { printf("d && e is TRUE (logical AND)\n"); } /* Mistake 4: Comma operator */ int y; y = 1, 2, 3; // Evaluated as (y = 1), 2, 3 printf("y = 1, 2, 3 => y = %d\n", y); printf(" ((y = 1), 2, 3) => y = 1\n"); } Explanation
demonstratePrecedence: Shows how different precedence levels affect evaluation.demonstrateAssociativity: Shows left-to-right and right-to-left evaluation.demonstrateParentheses: Shows how parentheses override default precedence.demonstrateCommonMistakes: Highlights typical errors like comparison chaining and assignment vs. equality.
Expected Output (partial)
text
=== Operator Precedence and Associativity === 1. Demonstrate Precedence 2. Demonstrate Associativity 3. Demonstrate Parentheses Effect 4. Demonstrate Common Mistakes 5. Exit Enter choice: 1 === Operator Precedence === 10 + 20 * 30 = 610 (20 * 30 evaluated first, then + 10) 5 << 2 > 10 = 1 ((5 << 2) > 10) = (20 > 10) = 1 5 > 3 == 1 = 1 ((5 > 3) == 1) = (1 == 1) = 1 5 & 3 == 3 = 1 (5 & (3 == 3)) = (5 & 1) = 1 Note: == has higher precedence than & (5 & 3) == 3 = 0 ((5 & 3) == 3) = (1 == 3) = 0 1 || 0 && 0 = 1 (1 || (0 && 0)) = (1 || 0) = 1
Common Pitfalls, Edge Cases & Memory Considerations
1. Comparison Chaining Does Not Work
In Python, a < b < c works as expected. In C, it evaluates as (a < b) < c.
c
int a = 10, b = 20, c = 30; if (a < b < c) { // (10 < 20) < 30 = 1 < 30 = 1 (true) } if (c > b > a) { // (30 > 20) > 10 = 1 > 10 = 0 (false) } Solution: Use logical operators: if (a < b && b < c).
2. Assignment vs. Equality
Using = instead of == in conditions is a classic bug.
c
if (a = 10) { // Assigns 10 to a, always true } Solution: Use == for comparison. Enable compiler warnings (-Wall).
3. Bitwise vs. Logical Operators
& has lower precedence than ==, causing unexpected grouping.
c
if (a & b == 0) { // Parsed as a & (b == 0) } Solution: Use parentheses: if ((a & b) == 0).
4. Postfix vs. Prefix Increment
Postfix ++ has higher precedence than prefix ++, but mixing them on the same variable is undefined.
c
int a = 5; int result = a++ + ++a; // Undefined behavior
Solution: Avoid multiple side effects on the same variable in one expression.
5. Comma Operator Precedence
The comma operator has the lowest precedence. It can cause unexpected results.
c
int a = 1, 2, 3; // a = 1, then 2 and 3 discarded
Solution: Use parentheses to group comma expressions: int a = (1, 2, 3);
6. Ternary Operator Associativity
The ternary operator is right-associative, which can lead to surprising results.
c
int result = a > b ? 10 : c > d ? 20 : 30; // Parsed as: a > b ? 10 : (c > d ? 20 : 30)
Solution: Use parentheses to clarify nesting.
7. Shift Operator Precedence
Shift operators have lower precedence than arithmetic operators, which can cause unexpected results.
c
int result = 1 << 2 + 3; // Parsed as 1 << (2 + 3) = 1 << 5 = 32
Solution: Use parentheses: (1 << 2) + 3.
8. Function Call Order is Unspecified
Precedence does not determine the order in which function arguments are evaluated.
c
int result = f1() + f2() * f3(); // f1(), f2(), f3() may be called in any order
Solution: Avoid side effects in function arguments that depend on evaluation order.
9. sizeof and Side Effects
sizeof does not evaluate its operand (except for variable-length arrays in C99).
c
int a = 5; size_t s = sizeof(a++); // a is NOT incremented printf("%d\n", a); // 5 Solution: Do not rely on side effects inside sizeof.
10. Memory Considerations
- Precedence and associativity do not affect memory directly.
- Misunderstanding precedence can lead to logic errors, not memory errors.
- Using parentheses improves readability without any performance cost.
Summary & Best Practices
Operator precedence and associativity are fundamental rules in C that determine how expressions are evaluated. Mastering them is essential for writing correct and predictable code.
Key Takeaways
- Precedence determines which operator is evaluated first.
- Associativity determines evaluation order for operators of the same precedence.
- Parentheses have the highest precedence and can override default ordering.
- Precedence and associativity determine grouping, not operand evaluation order.
- Common mistakes include comparison chaining, assignment vs. equality, and bitwise vs. logical confusion.
Best Practices
- Use parentheses liberally to clarify intent, even when not strictly required.
- Avoid side effects (like
++) on the same variable in complex expressions. - Use
==for comparison and=for assignment; enable compiler warnings. - Do not chain comparison operators; use
&&or||. - Parenthesize bitwise operations when mixed with relational/equality operators.
- Be cautious with the comma operator; use it only where intended (e.g., in
forloops). - Use explicit grouping in ternary operator expressions.
- Remember that
sizeofdoes not evaluate its operand. - Test expressions with different precedence to verify understanding.
- Document non-obvious expressions with comments explaining the evaluation order.
By mastering operator precedence and associativity, you’ll write C code that is both correct and easy to understand, avoiding subtle bugs that stem from misinterpreting how expressions are evaluated.



