ProgrammingSenior Embedded C Engineer

Explain the mechanism of operator precedence and associativity in C. How to correctly determine the order of evaluation in complex expressions and what traps await programmers when misusing operators of different precedence?

Pass interviews with Hintsage AI assistant

Answer.

Background

Operator precedence was introduced in C to control the order of evaluation in mathematical and logical expressions. Even though mathematical operators have historically expected precedence (as in algebra), the emergence of many new operators (logical, bitwise, assignment, etc.) has complicated the picture. To reduce the likelihood of errors and enhance readability, an official list of operator precedence and associativity was introduced.

Problem

Due to the larger number of operators and their different natures (arithmetic, comparisons, assignments, logical, indexing), ambiguities arise when forming complex expressions. A wrong understanding of the order of operations leads to logical errors and not always obvious bugs, especially when combining bitwise and logical operators, pointers, increments, and the ternary operator.

Solution

  • Each operator has a fixed precedence (the higher, the earlier it is applied in the expression).
  • If two operators have the same precedence, associativity applies (usually left to right).
  • To ensure safe and clear code, it is recommended to use parentheses to explicitly indicate the desired order of evaluation, even if you are confident in the precedence.
  • One should not rely on non-obvious precedence between operators (for example, between && and ||, between == and =, between & and ==).

Example code:

#include <stdio.h> int main() { int a = 1, b = 2, c = 3, d; d = a + b * c; // b*c is evaluated first: d = 1 + (2*3) = 7 printf("%d\n", d); d = a + b << 1; // a + b = 3, then 3 << 1 (6) printf("%d\n", d); d = a < b ? a++ : b++; printf("%d\n", d); // a < b is true => d = a (1), a will increase afterwards }

Key features:

  • Precedence affects the order of evaluation within the expression
  • Associativity is the rule of grouping when operator precedences are the same
  • Non-obvious combinations of operators can lead to bugs if parentheses are not used

Trick questions.

What result will the expression x = y > z ? y : z; give if parentheses are forgotten?

Answer: The ternary operator ?: has lower precedence than >. First, (y > z) is evaluated, and then the selection is made between y and z. However, if combined with assignment, unexpected effects can occur. It is better to always use parentheses x = (y > z) ? y : z;.

*What result will the expression p++ give and why?

Answer: The post-increment operator (++) has higher precedence than dereferencing (*), so *p++ becomes *(p++): first, p is used and incremented, only then dereferenced, which can differ from *++p (dereferencing after increment).

Why doesn’t the expression a & b == c work as expected?

Answer: The operator == has higher precedence than &, so the expression is analyzed as a & (b == c), not (a & b) == c, leading to unexpected results. Parentheses are needed for the desired logic.

if ((a & b) == c) { ... }

Common mistakes and anti-patterns

  • Using expressions without parentheses in non-obvious combinations of operators
  • Expecting one order of evaluation when, due to precedence, it is different
  • Confusion between & (bitwise AND) and && (logical AND), and == (comparison) and = (assignment)

Real-life example

Negative case

When optimizing code, a programmer combined multiple operators without parentheses: if( mask & flag == 0 ) ..., resulting in incorrect logic and leading to system failure.

Pros:

  • Shorter code

Cons:

  • Traps in precedence, hard-to-detect logical error

Positive case

Using explicit grouping: if( (mask & flag) == 0 ) ..., logic is transparent, flags are easy to change.

Pros:

  • Transparent behavior
  • Code is safe from errors during modification

Cons:

  • More parentheses, sometimes less elegantly visually, but significantly more reliable