Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Output evaluation order with embedded function calls

Tags:

c++

gcc

mingw

I'm a TA for an intro C++ class. The following question was asked on a test last week:

What is the output from the following program:

int myFunc(int &x) {
   int temp = x * x * x;
   x += 1;
   return temp;
}

int main() {
   int x = 2;
   cout << myFunc(x) << endl << myFunc(x) << endl << myFunc(x) << endl;
}

The answer, to me and all my colleagues, is obviously:

8
27
64

But now several students have pointed out that when they run this in certain environments they actually get the opposite:

64
27
8

When I run it in my linux environment using gcc I get what I would expect. Using MinGW on my Windows machine I get what they're talking about. It seems to be evaluating the last call to myFunc first, then the second call and then the first, then once it has all the results it outputs them in the normal order, starting with the first. But because the calls were made out of order the numbers are opposite.

It seems to me to be a compiler optimization, choosing to evaluate the function calls in the opposite order, but I don't really know why. My question is: are my assumptions correct? Is that what's going on in the background? Or is there something totally different? Also, I don't really understand why there would be a benefit to evaluating the functions backwards and then evaluating output forward. Output would have to be forward because of the way ostream works, but it seems like evaluation of the functions should be forward as well.

Thanks for your help!

like image 562
Eric Lifka Avatar asked Oct 01 '09 15:10

Eric Lifka


People also ask

What is evaluation order of function parameters in C?

evaluation order and sequence pointsOrder of evaluation of the operands of any C operator, including the order of evaluation of function arguments in a function-call expression, and the order of evaluation of the subexpressions within any expression is unspecified (except where noted below).

What is order of evaluation in C?

Order of evaluation refers to the operator precedence and associativity rules according to which mathematical expressions are evaluated.

What can be used to change the order of evaluation expression in C?

The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again.

Can be used to change the order of evaluation of expression?

Answer. Answer: To change the order of evaluation, enclose in parentheses the part of the formula to be calculated first.


2 Answers

The C++ standard does not define what order the subexpressions of a full expression are evaluated, except for certain operators which introduce an order (the comma operator, ternary operator, short-circuiting logical operators), and the fact that the expressions which make up the arguments/operands of a function/operator are all evaluated before the function/operator itself.

GCC is not obliged to explain to you (or me) why it wants to order them as it does. It might be a performance optimisation, it might be because the compiler code came out a few lines shorter and simpler that way, it might be because one of the mingw coders personally hates you, and wants to ensure that if you make assumptions that aren't guaranteed by the standard, your code goes wrong. Welcome to the world of open standards :-)

Edit to add: litb makes a point below about (un)defined behavior. The standard says that if you modify a variable multiple times in an expression, and if there exists a valid order of evaluation for that expression, such that the variable is modified multiple times without a sequence point in between, then the expression has undefined behavior. That doesn't apply here, because the variable is modified in the call to the function, and there's a sequence point at the start of any function call (even if the compiler inlines it). However, if you'd manually inlined the code:

std::cout << pow(x++,3) << endl << pow(x++,3) << endl << pow(x++,3) << endl;

Then that would be undefined behavior. In this code, it is valid for the compiler to evaluate all three "x++" subexpressions, then the three calls to pow, then start on the various calls to operator<<. Because this order is valid and has no sequence points separating the modification of x, the results are completely undefined. In your code snippet, only the order of execution is unspecified.

like image 52
Steve Jessop Avatar answered Sep 28 '22 02:09

Steve Jessop


Exactly why does this have unspecified behaviour.

When I first looked at this example I felt that the behaviour was well defined because this expression is actually short hand for a set of function calls.

Consider this more basic example:

cout << f1() << f2();

This is expanded to a sequence of function calls, where the kind of calls depend on the operators being members or non-members:

// Option 1:  Both are members
cout.operator<<(f1 ()).operator<< (f2 ());

// Option 2: Both are non members
operator<< ( operator<<(cout, f1 ()), f2 () );

// Option 3: First is a member, second non-member
operator<< ( cout.operator<<(f1 ()), f2 () );

// Option 4: First is a non-member, second is a member
cout.operator<<(f1 ()).operator<< (f2 ());

At the lowest level these will generate almost identical code so I will refer only to the first option from now.

There is a guarantee in the standard that the compiler must evaluate the arguments to each function call before the body of the function is entered. In this case, cout.operator<<(f1()) must be evaluated before operator<<(f2()) is, since the result of cout.operator<<(f1()) is required to call the other operator.

The unspecified behaviour kicks in because although the calls to the operators must be ordered there is no such requirement on their arguments. Therefore, the resulting order can be one of:

f2()
f1()
cout.operator<<(f1())
cout.operator<<(f1()).operator<<(f2());

Or:

f1()
f2()
cout.operator<<(f1())
cout.operator<<(f1()).operator<<(f2());

Or finally:

f1()
cout.operator<<(f1())
f2()
cout.operator<<(f1()).operator<<(f2());
like image 33
Richard Corden Avatar answered Sep 28 '22 02:09

Richard Corden