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?
the final value that gets assigned to x varies between systems, making the behaviour undefined.
Hm, it's the other way round. The behavior is undefined, so it can vary between systems, the compiler can do what it wants.the order of evaluation may be
When it's undefined, it's undefined, anything may be. Compiler may calculate what it wants, how it wants. You can write "then the compiler will format your hard drive" or "compiler will spawn demons" and it will be as much correct as any evaluation order in case of undefined behavior. – Centripetal&&
etc of course. – Marc