Kushagr Jaiswal
Kushagr Jaiswal

Reputation: 209

Operator precedence versus order of evaluation

A friend asked me to explain the difference between operator precedence and order of evaluation in simple terms. This is how I explained it to them :-

Let's take an example -

int x;
int a = 2;
int b = 5;
int c = 6;
int d = 4;

x = a * b / (c + d);

Here, the final value of x will become 1. This is because first, the values of c and d will be added together (6+4), then the values of a and b will be multiplied together (2*5), and finally, the division will take place (10/10), resulting in the final value becoming 1, which is then assigned to x.

All of this is specified by operator precedence. In this example, the parentheses force the addition to take place before the multiplication and the division, even though addition has a lower precedence. Also, the multiplication is executed before the division, because multiplication and division have the same precedence, and both of them have the associativity of left-to-right.

Now comes the important part, i.e. the order of evaluation of this expression.

On one system, the order of evaluation may be like this -

/* Step 1 */   x = a * b / (c + d);
/* Step 2 */   x = a * 5 / (c + d);
/* Step 3 */   x = a * 5 / (c + 4);
/* Step 4 */   x = a * 5 / (6 + 4);
/* Step 5 */   x = a * 5 / 10;
/* Step 6 */   x = 2 * 5 / 10;
/* Step 7 */   x = 10 / 10;
/* Step 8 */   x = 1;

Note that in any step, it is always ensured that the operator precedence is maintained, i.e. even though b was replaced by 5 in Step 2, the multiplication did not take place until Step 7. So, even though the order of evaluation is different for different systems, the operator precedence is always maintained.

On another system, the order of evaluation may be like this -

/* Step 1 */   x = a * b / (c + d);
/* Step 2 */   x = a * b / (6 + d);
/* Step 3 */   x = a * b / (6 + 4);
/* Step 4 */   x = a * b / 10;
/* Step 5 */   x = 2 * b / 10;
/* Step 6 */   x = 2 * 5 / 10;
/* Step 7 */   x = 10 / 10;
/* Step 8 */   x = 1;

Again, the operator precedence is maintained.

In the above example, the entire behaviour is well-defined. One reason for this is that all of the variables are different. In technical terms, the behaviour in this example is well-defined because there are no unsequenced modifications to any variable. So, on any system, x will always get assigned the value 1 finally.

Now, let's change the above example to this :-

int x;
int y = 1;

x = ++y * y-- / (y + y++);

Here, the final value that gets assigned to x varies between systems, making the behaviour undefined.

On one system, the order of evaluation may be like this -

/* Step 1 */   x = ++y * y-- / (y + y++);   // (y has value 1)
/* Step 2 */   x = ++y * y-- / (1 + y++);   // (y still has value 1)
/* Step 3 */   x = ++y * 1 / (1 + y++);     // (y now has value 0)
/* Step 4 */   x = 1 * 1 / (1 + y++);       // (y now has value 1)
/* Step 5 */   x = 1 * 1 / (1 + 1);         // (y now has value 2)
/* Step 6 */   x = 1 * 1 / 2;
/* Step 7 */   x = 1 / 2;
/* Step 8 */   x = 0;

Again, the operator precedence is maintained.

On another system, the order of evaluation may be like this -

/* Step 1 */   x = ++y * y-- / (y + y++);   // (y has value 1)
/* Step 2 */   x = ++y * y-- / (y + 1);     // (y now has value 2)
/* Step 3 */   x = ++y * 2 / (y + 1);       // (y now has value 1)
/* Step 4 */   x = ++y * 2 / (1 + 1);       // (y still has value 1)
/* Step 5 */   x = ++y * 2 / 2;             // (y still has value 1)
/* Step 6 */   x = 2 * 2 / 2:               // (y now has value 2)
/* Step 7 */   x = 4 / 2;
/* Step 8 */   x = 2;

Again, the operator precedence is maintained.

How can I improve this explanation?

Upvotes: 5

Views: 1784

Answers (4)

Luis Colorado
Luis Colorado

Reputation: 12668

Nope, you say

Here, the final value of x will become 1. This is because first, the values of c and d will be added together (6+4), then the values of a and b will be multiplied together (2*5), and finally, the division will take place (10/10), resulting in the final value becoming 1, which is then assigned to x.

the evaluation order establishes that 6 + 4 will be evaluated before the division is done... but not that the compiler cannot first arrange to evaluate first c * d (because the multiplication operators are left associative, and this means --also-- that the multiplication will be made before the division). You don't even know (except if you look at the assembler output) which order of subexpression evaluation will the compiler select. As stated, the full parenthesized expression would be:

(x = ((a * b) / (c + d)));

so, the compiler will decide to start first with a * b or c + d indistinctly. Then it will do the other operation, then it will do the division, and finally the assignment. But beware, because the assignment requires the address of x and not its value (it's an lvalue), so the address of x can be calculated at any point, but before the assignment is made. Finally, the (unused) value of the assignment is thrown.

a possible order could be:

  • calculate a * b
  • calculate address of x
  • calculate c + d
  • calculate the division (a*b)/(c+d)
  • store the result at position &x.

a different one:

  • calculate c + d
  • calculate a * b
  • calculate the division (a*b)/(c+d)
  • calculate address of x
  • store the result at position &x.

but you could also calculate the address of x in the first step.

Upvotes: 2

John Bode
John Bode

Reputation: 123458

6.5 Expressions
...
3     The grouping of operators and operands is indicated by the syntax.85) Except as specified later, side effects and value computations of subexpressions are unsequenced.86)
85) The syntax specifies the precedence of operators in the evaluation of an expression, which is the same as the order of the major subclauses of this subclause, highest precedence first. Thus, for example, the expressions allowed as the operands of the binary + operator (6.5.6) are those expressions defined in 6.5.1 through 6.5.6. The exceptions are cast expressions (6.5.4) as operands of unary operators (6.5.3), and an operand contained between any of the following pairs of operators: grouping parentheses () (6.5.1), subscripting brackets [] (6.5.2.1), function-call parentheses () (6.5.2.2), and the conditional operator ? : (6.5.15). Within each major subclause, the operators have the same precedence. Left- or right-associativity is indicated in each subclause by the syntax for the expressions discussed therein.

86) In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations.
C 2011 Online Draft

Precedence and associativity only control how expressions are parsed and which operators are grouped with which operands. They do not control the order in which subexpressions are evaluated.

Given your example

x = a * b / (c + d);

precedence and associativity cause the expression to be parsed as

(x) = ((a * b) / (c + d))

The multiplicative operators * and / have the same precedence and are left-associative, so a * b / (c + d) is parsed as (a * b) / (c + d) (as opposed to a * (b / (c + d))).

So what this tells us is that the result of a * b is divided by the result of c + d, but this does not mean that a * b must be evaluated before c + d or vice versa.

Each of a, b, c, and d may be evaluated in any order (including simultaneously if the architecture supports it). Similarly each of a * b and c + d may be evaluated in any order, and if the same expression is evaluated multiple times in the program, that order doesn't have to be consistent. Obviously both a and b have to be evaluated before a * b can be evaluated, and both c and d have to be evaluated before c + d can be evaluated, but that's the only ordering you can be certain about.

There are operators that force left-to-right evaluation - ||, &&, ?:, and the comma operator, but in general order of evaluation is a free-for-all.

Upvotes: 5

4386427
4386427

Reputation: 44274

I would prefer an explanation that uses function calls. A function call makes it very obvious that "something needs to be evaluated before applying the operator".

Basic example:

int x = a() + b() * c();

must be calculated as

temp = result_of_b_func_call * result_of_c_func_call
x = result_of_a_func_call + temp

due to multiplication having higher precedence than addition.

However, the evaluation order of the 3 function calls is unspecified, i.e. the functions can be called in any order. Like

a(), b(), c()
or
a(), c(), b()
or
b(), a(), c()
or
b(), c(), a()
or
c(), a(), b()
or
c(), b(), a()

Another basic example would be to explain operator associativity - like:

int x = a() + b() + c();

must be calculated as

temp = result_of_a_func_call + result_of_b_func_call
x = temp + result_of_c_func_call

due to left-to-right associativity of addition. But again the order of the 3 function calls are unknown.

If function calls is not an option, I would prefer something like

x = a * b + c / d

Here it's pretty obvious that there are two sub-expressions, i.e. a * b and c / d. Due to operator precedence both of these sub-expressions must be evaluated before the addition but the order of evaluation is unspecified, i.e. we can't tell whether the multiplication or the division is done first.

So it can be

temp1 = a * b
temp2 = c / d
x = temp1 + temp2

or it can be

temp2 = c / d
temp1 = a * b
x = temp1 + temp2

All we know is that the addition must be last.

Upvotes: 10

sj95126
sj95126

Reputation: 6908

It's not necessarily true to say that the "the parentheses force the addition to take place before the multiplication and the division". You can see this in a disassembly of the code (gcc 10.2.0):

x = a * b / (c + d);
   1004010b6:   8b 45 fc                mov    -0x4(%rbp),%eax
   1004010b9:   0f af 45 f8             imul   -0x8(%rbp),%eax
   1004010bd:   8b 4d f4                mov    -0xc(%rbp),%ecx
   1004010c0:   8b 55 f0                mov    -0x10(%rbp),%edx
   1004010c3:   01 d1                   add    %edx,%ecx
   1004010c5:   99                      cltd
   1004010c6:   f7 f9                   idiv   %ecx

The multiplication was performed first, followed by the addition, then the division.

Upvotes: 2

Related Questions