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 provide every operator with an association attribute. The association describes whether the operators 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:
- Perform a task (the name of the operator is indicative).
- 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 produce 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.
Definition — Side-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 operand with the highest ranked type.
Most operators return rvalues, i.e., values that cannot be modified. Only a few, like the indirection operator, can potentially result in lvalues (expressions representing modifiable memory). Conceptually, you can think of operators as functions returning values. Some operators, like member selection, 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 apply to subscripting, 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 considers 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. Significantly, the assignment operators, and the unary prefix operators, associate from right-to-left. This is illustrated in the following code extract:
0; a = b = c =
The expression in the last statement is evaluated as follows:
0))); (a = (b = (c =
On the other hand, postfix inc/decrement has higher precedence than indirection, so association does not apply in the following expression:
x = *p++;// same x = *(p++);
However, prefix inc/decrement and indirection have the same precedence, therefore operand association does play a role:
// evaluated as: `++(*p)`
x = ++*p; // evaluated as: `*(++p)` x = *++p;
Understanding operand association direction, is as important as understanding and learning the operator precedence table.
Sequence Guarantees
It is important to understand that many operators, in particular the arithmetic operators, do not provide 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 because the addition operator does not provide a sequence guarantee. Neither of the functions 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), ,
(comma), 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 sequence 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 evaluated 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 result of the 2nd operand, or the 3rd operand, based on the logical value of the 1st operand. It therefor 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 NOT │ Complement (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]