Could someone please explain to me why the following code prints '654' on line 11 instead of '456'? It just doesn't make any sense to me:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include<iostream>
int f(void)
{
staticint i = 3;
return ++i;
}
int main()
{
std::cout<<f()<<f()<<f();
std::cout<<std::endl;
int i = 3;
std::cout<<++i<<++i<<++i;
while(true);
return 0;
}
And why does line 14 print '666' instead of '456'?
line 11 invokes unspecified behavior: the numbers 4, 5, and 6 may be printed in any order.
line 14 invokes undefined behavior. Attempting to execute a program with that line is an error, the compiler is free to crash or do whatever (in practice, some numbers are printed)
I'll run these in a few compilers for demo purposes (without the while(true) loop of course)
OS: Linux
GNU g++ 4.6.2 prints 654 666
Clang++ 3.0 prints 456 456
Intel C++ 11.1 prints 456 456
OS: Solaris
Sun C++ 5.8 prints 456 666
GNU g++ 3.4.6 prints 456 456
std::cout<<f()<<f()<<f(); is actually ( ( std::cout << f() ) << f() ) << f();
similarly
std::cout<<++i<<++i<<++i; is ( ( std::cout << ++i ) << ++i ) << ++i;
so the compiler will produce a parse tree something like this
<<
/ \
<< ++i
/ \
<< ++i
/ \
cout ++i
my guess it it then has to fill in the ++i parts before evaluating the whole tree and it looks like it does this breadth first.
So the top ++i becomes 4 then the next one becomes 5 - but wait - this is the same memory location so basically i is now 5
and then the last one makes i = 6 - then the whole tree is evaluated giving the printout 666
What about the f() case?
<<
/ \
<< f()
/ \
<< f()
/ \
cout f()
The top f() is evaluated giving 4 - this is stored in the return value from the function and put into the tree - so it stays at 4
then the next one gives 5 and the last one 6. The output is then 654
I don't use increment and decrement within expressions, even though I learnt C a long time ago.
my guess it it then has to fill in the ++i parts before evaluating the whole tree
The top f() is evaluated giving 4 - this is stored in the return value from the function and put into the tree - so it stays at 4 then the next one gives 5 and the last one 6.
Those are fairly plausible guesses at what did one version of one compiler on one platform do, but they are not applicable to the program in general. The order of evaluation in C++ (and C) is arbitrary. The compiler is even allowed to call the three f()'s in another order the next time it executes the same expression. And no guessing at all can be applied to line 14.
The general rule is you cannot write to a variable more than once between sequence points (typically, a sequence point is determined by a semicolon). Additionally, you cannot read a variable multiple times if it is written in the same sequence.
Your code is a good example of why this is illegal/undefined. The compiler is free to execute the sequence in any order it sees fit as long as operator precedence is maintained.
This sequence: std::cout<<f()<<f()<<f();
Is doing 6 things:
1) calling f1
2) calling f2
3) calling f3
4) sending the result of f1 to cout with the << operator
5) "" f2 ""
6) "" f3 ""
The only thing that is guaranteed by operator precendence here is that f will be evaluated before its output is sent to cout. That is, the () operator has higher precedence than the << operator.
This means that 123456 (as listed above) is a valid sequence. However 321456 (which produces the '654' output you were seeing) is also a valid sequence. Even something like 214356 would be a valid sequence.
This code: std::cout<<++i<<++i<<++i;
has the same issue. The only thing that's guaranteed is that the ++ will evaluate before the corresponding << evaluates. But nothing is said on the order in which the ++ are evaluated.
The compiler could interpret this any number of ways:
1 2 3 4
++i;
++i;
++i;
cout << i << i << i; // outputs '666'
Undefined behavior in this case opens the way for compiler optimization and performance increases.
A better solution would be to outright forbid such instances in code so that attempting to do this would produce a compiler error.
However that is difficult to enforce.
I don't think it's that big of a deal, personally. Just one more reason you shouldn't cram as much as possible onto one line of code. People that do that are shooting themselves in the foot anyway.