Operator Precedence

Relative Rank of C99 Operators

C99 has fewer operators than C++, but the same operators have the same precedence relative to each other. The precedence by itself is not sufficient, which is why both C99 and C++ also pro­vide every operator with an association attribute. The association describes whether the op­e­ra­tors associate with their operands from left-to-right (the norm), or from right-to-left.

PREREQUISITES — You should already…
  • have some C programming experience.
  • understand the concept of C lvalues, rvalues, expressions.

Introduction

It is important to understand the behaviour of operators as a two step process:

  1. Perform a task (the name of the operator is indicative).
  2. Result in a value / return a value.

Once that is understood, it is then simple to explain the difference between, for example, the pre-increment and post-increment operators: They perform the exact same task or job, but pro­duce different results. So, if the return value is not used in an expression, it is irrelevant whether the pre-, or post-fix operator is used.

DefinitionSide-effect

Some operators have a side-effect, which means that, as part of the task they perform, they (also) modify memory.

Operand Types & Returns

All operators have rules regarding the type of their operands. Some operators require operands of the same type, like the arithmetic operators. The compiler will promote a smaller ranked type to the other operand's type, before the operator is invoked. The result will be the type of the op­e­rand with the highest ranked type.

Most operators return rvalues, i.e., values that cannot be modified. Only a few, like the in­di­rec­tion op­e­ra­tor, can potentially result in lvalues (expressions representing modifiable memory). Con­cep­tu­ally, you can think of operators as functions returning values. Some operators, like mem­ber se­lec­tion, and indirect member selection, will return lvalues, if the left hand operand is an lvalue, or points to an lvalue. Subscripting is just indirection in disguise, so all rules relating to indirection, also ap­ply to sub­script­ing, such as: produce lvalues, if the left hand operand is a not a void* type, or a const T* type.

Operand Association or Grouping

When the compiler must choose between two operators with the same level of precedence, it con­sid­ers how they associate with respect to their operands: from left-to-right, or right-to-left, and choose the first operator to create code for, on that basis. Most operators associate from left-to-right. Sig­ni­f­i­cant­ly, the assignment operators, and the unary prefix operators, associate from right-to-left. This is illustrated in the following code extract:

a = b = c = 0;

The expression in the last statement is evaluated as follows:

(a = (b = (c = 0)));

On the other hand, postfix inc/decrement has higher precedence than indirection, so as­so­cia­tion does not apply in the following expression:

x = *p++;
x = *(p++); // same

However, prefix inc/decrement and indirection have the same precedence, therefore op­e­rand association does play a role:

x = ++*p;  // evaluated as: `++(*p)`
x = *++p;  // evaluated as: `*(++p)`

Understanding operand association direction, is as important as understanding and learning the op­e­ra­tor precedence table.

Sequence Guarantees

It is important to understand that many operators, in particular the arithmetic operators, do not pro­vide any standardised order of evaluation of the operands. In the following code extract, which of the two operand expressions are evaluated first, f() or g(), is implementation defined. That is be­cause the addition operator does not provide a sequence guarantee. Neither of the func­tions should depend on side-effects caused by the other.

int result = f() + g();

A handful of operators provide a sequence guarantee: && (logical AND), || (logical OR), , (com­ma), and ?: (conditional). Their left hand operands are guaranteed to be evaluated first.

bool result = f() && g();

In the above statement, the f() expression is guaranteed to be evaluated first, because of the se­quen­ce guarantee provided by the logical OR operator (&&).

The binary logical operators, and the conditional operator, do not automatically evaluate all their operands. For the two logical operators (&& and ||), the right hand operand is only eva­lu­at­ed if the result cannot be determined from the first operand. This is called short-circuiting.

The conditional operator takes three operands. The result of the operator, will be either the re­sult of the 2nd ope­rand, or the 3rd operand, based on the logical value of the 1st operand. It there­for only ever evaluates two operands, the first, and based in its result, one of the other two.

The comma operator is easily abused, leading to unreadable and buggy code, and should be only be used in well-established, recognisable, patterns.

Operator Precedence Table

Arranged in decreasing priority. Operators on the same level, have the same association.

N Operator Name Ass.
1 L ++L -- Post-inc/decrement. L: lvalue L→R
F ( A ) Function call. F: function ptr., A: arguments.
P [ I ] Subscript. P: pointer expr., I: integer.
S . M Member select. S: structured expr., M: member identifier.
P -> M Indirect member select. E: pointer expr., M: member identifier.
(T){L} C99 compound literal. T: Type, L: list.
2 ++ LV-- LV Pre-inc/decrement. LV: lvalue R→L
- N+ N Negation.Identity N: numerical type expr.
! B~ I Logical NOTComplement (bitwise NOT) B: bool, I: integer.
( T ) E Explicit cast. T: type, E: expression.
* P Indirection. P: pointer expr.
& M Address-of. M: Expr. representing memory.
sizeof( T ) Sizeof Type. T: type.
sizeof E Sizeof Expression. E: expr.
3 A1 * A2 Multiplication. A: arithmetic expr. L→R
A1 / A2 Division. A: arithmetic expr.
I1 % I2 Modulus / Remainder. I: integer expr.
4 A1 * A2 Addition. A: arithmetic expr. L→R
A1 / A2 Subtraction. A: arithmetic expr.
5 I1 << I2 Bitwise Left-Shift. I: integer expr. L→R
I1 >> I2 Bitwise Right-Shift. I: integer expr.
6 E1 < E2 Less Than / Smaller Than. E: expr. L→R
E1 > E2 Greater Than / Bigger Than. E: expr.
E1 <= E2 Less Than or Equal. E: expr.
E1 >= E2 Greater Than or Equal. E: expr.
7 E1 == E2 Equality. E: expr. L→R
E1 != E2 Inequality. E: expr.
8 I1 & I2 Bitwise AND. I: integer expr. L→R
9 I1 ^ I2 Bitwise XOR. I: integer expr. L→R
10 I1 | I2 Bitwise OR. I: integer expr. L→R
11 B1 && B2 Logical AND. B: _Bool expr. L→R
12 B1 || B2 Logical OR. B: _Bool expr. L→R
13 Eb ? Et : Ef Conditional. El: _Bool expr., Et: “true” expr., Ef: “false” expr. R→L
14 L = E Assignment. L: lvalue expr., E: expr. R→L
L ▢= E Compound Assignment. L: lvalue expr., E: expr.,
: + - * / % << >> & ^ |
15 E1 , E2 Comma. E: expr. L→R

Knowing your operator precedence table is the secret to a happy life.


2017-11-18: Update to new admonitions. [brx]
2017-09-23: Created. Edited [brx;jjc]