Why do different C++ compilers give different results for this code?

4.6k Views Asked by At

I'm writing some C++ codes for fun and practice, to learn more about language features. I want to know more about static variables and their behaviour in recursive functions. Trying this code in g++ compiler, I'm given expected result:

#include <iostream>
using namespace std;

int f(const int& value)
{
   static int result = 0;
   return result += value;
}

int main()
{
   cout << f(10) << ", " << f(f(10)) << ", " << f(f(f(10)));
   return 0;
}

But my friend tested same code in Microsoft Visual C++ 6. output is 50, 80, 90 I tested it with other C++ compilers (g++, Borland, Code::blocks and MingW under Linux, Win and Mac) output was 110, 100, 40. I can't understand how output could be 50, 80, 90 ...

Why MSVC's output is different?

4

There are 4 best solutions below

1
On BEST ANSWER

The order of evaluation of the following three subexpressions is unspecified:

f(10)
f(f(10))
f(f(f(10)))

The compiler may evaluate those subexpressions in any order. You should not rely on a particular order of evaluation in your program, especially if you intend to compile using multiple compilers.

This is because there is no sequence point anywhere in that expression. The only requirement is that each of those subexpressions is evaluated before the result is needed (that is, before the result is to be printed).

In your example, there are actually several subexpressions, which I've labelled as a through k here:

//   a  b     c       d  e f      g       h  i j k
cout << f(10) << ", " << f(f(10)) << ", " << f(f(f(10)));

The calls to operator<< (a, c, d, g, and h) all have to be evaluated in order because each depends on the result of the previous call. Likewise, b has to be evaluated before a can be evaluated, and k has to be evaluated before j, i, or h can be evaluated.

However, there are no dependencies between some of these subexpressions: the result of b is not dependent upon the result of k, so the compiler is free to generate code that evaluates k then b or b then k.

For more information on sequence points and related unspecified and undefined behavior, consider reading the Stack Overflow C++ FAQ article, "Undefined Behavior and Sequence Points" (your program doesn't have any undefined behavior, but much of the article still applies).

2
On

Just because the output appears left-to-right on the screen does not mean the order of evaluation follows the same direction. In C++, the order of evaluation of function arguments is unspecified. Plus, printing data via the << operator is just fancy syntax for calling functions.

In short, if you say operator<<(foo(), bar()), the compiler can call foo or bar first. That's why it's generally a bad idea to call functions with side effects and use those as arguments to other functions.

1
On

An easy way to see exactly what it is doing:

int f(const int& value, int fID)
{
   static int result = 0;
   static int fCounter = 0;
   fCounter++;
   cout << fCounter << ".  ID:" << fID << endl;    
   return result += value;
}

int main()
{
   cout << f(10, 6) << ", " << f(f(10, 4), 5) << ", " << f(f(f(10, 1),2),3);
   return 0;
}

I agree with what others have said in their answers, but this would allow you to see exactly what it is doing. :)

0
On

The prefix operator syntax is translated into the following prefix notation:

<<( <<( <<( cout, f(10) ), f(f(10)) ), f(f(f(10))) )
 A   B   C

Now there are three different function calls, identified as A, B and C above. with the arguments of each call being:

     arg1        arg2
A: result of B, f(10)
B: result of C, f(f(10))
C: cout       , f(f(f(10)))

For each one of the calls, the compiler is allowed to evaluate the arguments in any order, for the correct evaluation of the first argument of A, B has to be evaluated first, and similarly for the first argument of B, the whole C expression has to be evaluated. This implies that there is a partial order on the execution of A, B, and C required by the first argument dependency. There is also a partial ordering on the evaluation of each call and both arguments, so B1 and B2 (referring to the first and second arguments of the call B) have to be evaluated before B.

Those partial orderings do not lead to a unique requirement for the execution of the calls, since the compiler can decide to execute all second arguments before trying to evaluate the first argument, leading to the equivalent path:

tmp1 = f(10); tmp2 = f(f(10)); tmp3 = f(f(f(10)));
cout << tmp1 << tmp2 << tmp3;

or

tmp3 = f(f(f(10))); tmp2 = f(f(10)); tmp1 = f(10);
cout << tmp1 << tmp2 << tmp3;

or

tmp2 = f(f(10)); tmp1 = f(10); tmp3 = f(f(f(10)));
cout << tmp1 << tmp2 << tmp3;

or ... keep combining.