Search code examples
coperator-precedenceexpression-evaluation

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?


Solution

  • 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.