CS253: Software Development with C++

Spring 2022

Order Of Evaluation

Show Lecture.OrderOfEvaluation as a slide show.

CS253 Order Of Evaluation

made at imgflip.com

Basic Expressions

Consider the expression a+b, which parses as:

                                 ┌───┐
                                 │ + │
                                 └───┘
                                 ⠌   ⠡
                                ⠌     ⠡
                            ┌───┐     ┌───┐
                            │ a │     │ b │
                            └───┘     └───┘

The a and b nodes are children of the + node.

Basic Expressions

How does a+b*c parse?

                  ┌───┐                       ┌───┐
                  │ * │                       │ + │
                  └───┘                       └───┘
                  ⠌   ⠡                       ⠌   ⠡
                 ⠌     ⠡                     ⠌     ⠡
             ┌───┐     ┌───┐             ┌───┐     ┌───┐
             │ + │     │ c │             │ a │     │ * │
             └───┘     └───┘             └───┘     └───┘
             ⠌   ⠡                                 ⠌   ⠡
            ⠌     ⠡                               ⠌     ⠡
        ┌───┐     ┌───┐                       ┌───┐     ┌───┐
        │ a │     │ b │                       │ b │     │ c │
        └───┘     └───┘                       └───┘     └───┘

The right-hand side, of course. * has higher precedence than +, so a+b*c is treated as a+(b*c), not as (a+b)*c.

Basic Expressions

Let’s use parentheses. How does (a+b)*c parse?

                  ┌───┐                       ┌───┐
                  │ * │                       │ + │
                  └───┘                       └───┘
                  ⠌   ⠡                       ⠌   ⠡
                 ⠌     ⠡                     ⠌     ⠡
             ┌───┐     ┌───┐             ┌───┐     ┌───┐
             │ + │     │ c │             │ a │     │ * │
             └───┘     └───┘             └───┘     └───┘
             ⠌   ⠡                                 ⠌   ⠡
            ⠌     ⠡                               ⠌     ⠡
        ┌───┐     ┌───┐                       ┌───┐     ┌───┐
        │ a │     │ b │                       │ b │     │ c │
        └───┘     └───┘                       └───┘     └───┘

The left-hand side, of course. Parentheses mean “do this first”. The a+b must be evaluated before the c.

No

NO!

Really

              ┌───┐
              │ * │
              └───┘
              ⠌   ⠡
             ⠌     ⠡
         ┌───┐     ┌───┐
         │ + │     │ c │
         └───┘     └───┘
         ⠌   ⠡
        ⠌     ⠡
    ┌───┐     ┌───┐
    │ a │     │ b │
    └───┘     └───┘

Really

              ┌───┐
              │ * │
              └───┘
              ⠌   ⠡
             ⠌     ⠡
         ┌───┐     ┌───┐
         │ + │     │ c │
         └───┘     └───┘
         ⠌   ⠡
        ⠌     ⠡
    ┌───┐     ┌───┐
    │ a │     │ b │
    └───┘     └───┘

Possible orders of evaluation of (a+b)*c:
a, b, c, +, *
a, b, +, c, *
a, c, b, +, *
b, a, c, +, *
b, a, +, c, *
b, c, a, +, *
c, a, b, +, *
c, b, a, +, *

Why is this difficult?

              ┌───┐
              │ * │
              └───┘
              ⠌   ⠡
             ⠌     ⠡
         ┌───┐     ┌───┐
         │ + │     │ c │
         └───┘     └───┘
         ⠌   ⠡
        ⠌     ⠡
    ┌───┐     ┌───┐
    │ a │     │ b │
    └───┘     └───┘

Some Operators Are Different

A user-defined operation (e.g., operator+) does not have order determined, because it’s a function called with two arguments. The order of evaluation of function arguments is unspecified.

Short-circuiting operators

int n = time(nullptr) > 40 ? 2024'11'22 : sleep(60*60);
cout << n;
20241122

Pre-/Post-Increment/Decrement

This example is bad. Don’t do this:

int n = 1;
cout << ++n * ++n << endl;  // 🦡
c.cc:2: warning: operation on ‘n’ may be undefined
c.cc:2: warning: operation on ‘n’ may be undefined
9
Why not?

n is being modified twice in this expression, and the order of those modifications can’t be controlled.

Another bad example

Don’t do this, either:

short nums[] = {1, 2, 3, 4};

short *p = nums;
cout << *p++ << ' ';
cout << *p++ << '\n';

p = nums;
cout << *p++ << ' ' << *p++;  // 🦡
c.cc:8: warning: operation on ‘p’ may be undefined
1 2
1 2
Why not?

cout << *p++ << ' ' << *p++; is an expression, and p is being modified twice in that expression. The order of the various parts is unspecified. << is just another operator, like +, =, or /=.

A non-increment example

At this point, students think “Ah—increment is unpredictable. I’ll avoid it.” That’s not the only potential problem.

int inc() {             // Return increasing numbers: 1, 2, …
    static int n = 0;
    n = n + 1;
    return n;
}

int main() {
    int a[] = {inc(), inc(), 33, 44, 55}; // 🦡 1,2,33,44,55 or 2,1,33,44,55?
    a[inc()] = inc();                     // 🦡 a[3] = 4 or a[4] = 3?
    for (int n : a)
        cout << n << ' ';
}
1 2 33 44 3 

Note the lack of any sort of warning.

Why not?

Why not? Because, in all cases above, the variable is being modified twice in the same expression.

Another Example

int a() { cout << 'A'; return 1; }
int b() { cout << 'B'; return 2; }

int main() {
    cout << a()+b() << '\n';    // 🦡 Might display AB3 or BA3.
    cout << a() << b() << '\n'; // 🦡 Might display A1B2, AB12, BA12
    return 0;
}
AB3
A1B2

Experiments don’t matter

You cannot determine the “correct” result by experimentation.

It’s like when your grandmother asks you what kind of music you kids like these days. You certainly don’t represent everybody in your age group. Besides, your own tastes may change.

How to do it right

Break expressions apart, perhaps using explicit temporaries, to control the order of evaluation:

int a() { cout << 'A'; return 1; }
int b() { cout << 'B'; return 2; }

int main() {
    int c = a();    // Will display A
    c += b();       // Will display B
    cout << a();    // Will display A1
    cout << b();    // Will display B2
    return 0;
}
ABA1B2