Почему разные компиляторы C ++ дают разные результаты для этого кода? - PullRequest
1 голос
/ 01 марта 2011

Я пишу несколько кодов C ++ для развлечения и практики, чтобы узнать больше о языковых возможностях.Я хочу узнать больше о статических переменных и их поведении в рекурсивных функциях.Пробуя этот код в компиляторе g ++, я получаю ожидаемый результат:

#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;
}

Но мой друг тестировал тот же код в Microsoft Visual C ++ 6. Вывод: 50, 80, 90 Я тестировал его с другими компиляторами C ++ (g ++,Borland, Code :: blocks и MingW под Linux, Win и Mac) выводились 110, 100, 40.Я не могу понять, как вывод может быть 50, 80, 90 ...

Почему вывод MSVC отличается?

Ответы [ 4 ]

14 голосов
/ 01 марта 2011

Порядок вычисления следующих трех подвыражений не указан:

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

Компилятор может оценивать эти подвыражения в любом порядке. Вам не следует полагаться на определенный порядок оценки в вашей программе, особенно если вы собираетесь компилировать с использованием нескольких компиляторов.

Это потому, что в этом выражении нет ни одной точки последовательности. Единственное требование состоит в том, чтобы каждое из этих подвыражений оценивалось до того, как потребуется результат (то есть до того, как результат должен быть напечатан).

В вашем примере, на самом деле есть несколько подвыражений, которые я пометил здесь как сквозные:

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

Вызовы operator<< (a, c, d, g и h) должны быть оценены по порядку, так как каждый зависит от результата предыдущего вызова. Аналогично, b должен быть оценен до того, как a может быть оценен, и k должен быть оценен до того, как j, i или h могут быть оценены.

Однако между некоторыми из этих подвыражений нет никакой зависимости: результат b не зависит от результата k, поэтому компилятор может генерировать код, который оценивает k затем b или b, затем k.

Для получения дополнительной информации о точках последовательности и связанных с ними неопределенных и неопределенных режимах рассмотрите статью часто задаваемых вопросов о переполнении стека C ++, «Неопределенные точки поведения и точки последовательности» (ваша программа не имеет неопределенного поведения, но большая часть статьи все еще применяется).

3 голосов
/ 01 марта 2011

То, что вывод отображается слева на право на экране, не означает, что порядок оценки следует в том же направлении.В C ++ порядок вычисления аргументов функции равен неопределен .Кроме того, печать данных через оператор << - это просто причудливый синтаксис для вызова функций.

Короче говоря, если вы скажете operator<<(foo(), bar()), компилятор может сначала вызвать foo или bar.Вот почему вообще плохая идея вызывать функции с побочными эффектами и использовать их в качестве аргументов для других функций.

2 голосов
/ 01 марта 2011

Синтаксис префиксного оператора транслируется в следующую префиксную нотацию:

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

Теперь есть три различных вызова функций, обозначенных как A, B и C выше.с аргументами каждого вызова:

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

Для каждого из вызовов компилятору разрешено оценивать аргументы в любом порядке, для правильной оценки первого аргумента A, B долженоценивать в первую очередь, и аналогично для первого аргумента B, все выражение C должно быть оценено.Это подразумевает, что существует частичный порядок выполнения A, B и C, требуемый зависимостью первого аргумента.Существует также частичное упорядочение при оценке каждого вызова и обоих аргументов, поэтому B 1 и B 2 (ссылаясь на первый и второй аргументы вызова B) должны бытьоценивается до B.

Эти частичные упорядочения не приводят к уникальному требованию для выполнения вызовов, так как компилятор может решить выполнить все вторые аргументы перед попыткой вычислить первый аргумент, ведущий к эквивалентному пути:

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

или

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

или

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

или ... продолжайте комбинировать.

2 голосов
/ 01 марта 2011

Простой способ точно понять, что он делает:

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;
}

Я согласен с тем, что другие сказали в своих ответах, но это позволит вам точно увидеть, что он делает.:)

...