Show Lecture.OrderOfEvaluation as a slide show.
CS253 Order Of Evaluation
Basic Expressions
Consider the expression a+b
, which parses as:
┌───┐
│ + │
└───┘
⠌ ⠡
⠌ ⠡
┌───┐ ┌───┐
│ a │ │ b │
└───┘ └───┘
Basic Expressions
How does a+b*c
parse?
┌───┐ ┌───┐
│ * │ │ + │
└───┘ └───┘
⠌ ⠡ ⠌ ⠡
⠌ ⠡ ⠌ ⠡
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ + │ │ c │ │ a │ │ * │
└───┘ └───┘ └───┘ └───┘
⠌ ⠡ ⠌ ⠡
⠌ ⠡ ⠌ ⠡
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ a │ │ b │ │ b │ │ c │
└───┘ └───┘ └───┘ └───┘
The right-hand side, of course. *
has higher precedence than +
.
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”.
No
NO!
Really
┌───┐
│ * │
└───┘
⠌ ⠡
⠌ ⠡
┌───┐ ┌───┐
│ + │ │ c │
└───┘ └───┘
⠌ ⠡
⠌ ⠡
┌───┐ ┌───┐
│ a │ │ b │
└───┘ └───┘
- Parentheses do not mean “first”; they mean “together”.
- Consider
(w+x)*(y+z)
; can’t do both first.
- Parentheses determine the shape of the parse tree,
not the order of evaluation.
- Of course, the shape of the tree determines the order of
evaluation, to some extent.
- In this tree, the
+
node feeds to the *
node, so +
must execute before *
.
- However, we can evaluate
c
before the addition, or after.
Really
┌───┐
│ * │
└───┘
⠌ ⠡
⠌ ⠡
┌───┐ ┌───┐
│ + │ │ c │
└───┘ └───┘
⠌ ⠡
⠌ ⠡
┌───┐ ┌───┐
│ a │ │ b │
└───┘ └───┘
Possible orders of evaluation:
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 │
└───┘ └───┘
- We inherited the concept of precedence from mathematics, which has
no side effects, so our mathematical intuition misleads us.
- If
a
, b
, and c
are simple variables, then it doesn’t matter
in which order they are evaluated.
- However, if they’re expressions with side effects, then it does matter:
- increment/decrement
- assignment
- input/output
- function calls
- could change global variables
- could generate output
Some Operators Are Different
- Some operators have order of evaluation specified:
&& ||
,
(in an expression, not as an argument separator)
?:
- Most do not:
+ - * / %
<< >> & | ^
< > <= >= == !=
= += -= *= /= %= <<= >>= &= |= ^=
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.
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
9
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.
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.
// Return increasing numbers: 0, 1, …
int inc() {
static int n = 0;
return n++;
}
int main() {
int a[] = {inc(), inc(), 33, 44}; // 0,1,33,44 or 1,0,33,44?
a[inc()] = inc(); // a[2] = 3 or a[3] = 2?
for (int n : a)
cout << n << ' ';
}
0 1 33 2
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.
<<
is an operator, just like +
is.
cout << a << b
is one expression.
- same as
(cout << a) << b
- same as
operator<<(operator<<(cout,a), b)
- C++ does not determine the order of execution of the
various parts of the expression, except for a few special cases:
&&
||
?:
,
(the binary comma operator, not function arguments)
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.
- The compiler might do it in one order today, and in a different order tomorrow.
- The next version of the compiler might do it differently.
- It might depend on what other variables have been declared.
- A different compiler might do things in a different order.
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