1

私は楽しみと練習のためにいくつかの 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をテストしました110, 100, 40。出力がどのようになるのか理解できません50, 80, 90...

MSVC の出力が異なるのはなぜですか?

4

4 に答える 4

14

次の3つの部分式の評価の順序は指定されていません。

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

コンパイラーは、これらの部分式を任意の順序で評価できます。特に複数のコンパイラを使用してコンパイルする場合は、プログラムの特定の評価順序に依存しないでください。

これは、その式のどこにもシーケンスポイントがないためです。唯一の要件は、結果が必要になる前に(つまり、結果が出力される前に)、これらの各部分式が評価されることです。

あなたの例では、実際にはいくつかの部分式があり、ここではスルーkとラベル付けしています。

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

operator<<( 、、、、、、および)の呼び出しはすべてa、前の呼び出しの結果に依存するため、すべて順番に評価する必要があります。同様に、評価する前に評価する必要があり、、、またはを評価する前に評価する必要があります。cdghbakjih

ただし、これらの部分式の一部の間には依存関係はありません。の結果はのb結果に依存しないため、コンパイラーは、thenまたはthenkを評価するコードを自由に生成できます。kbbk

シーケンスポイントおよび関連する未定義および未定義の動作の詳細については、Stack Overflow C ++ FAQの記事「未定義の動作とシーケンスポイント」を読むことを検討 してください(プログラムには未定義の動作はありませんが、記事の多くは引き続き適用されます)。

于 2011-03-01T17:28:40.093 に答える
3

出力が画面の左から右に表示されるからといって、評価の順序が同じ方向に従うことを意味するわけではありません。C ++では、関数の引数の評価の順序は指定されていません。さらに、<<演算子を介してデータを出力することは、関数を呼び出すための単なる凝った構文です。

要するに、あなたが言うならoperator<<(foo(), bar())、コンパイラはfooまたはbar最初に呼び出すことができます。そのため、副作用のある関数を呼び出して、それらを他の関数の引数として使用することは一般的に悪い考えです。

于 2011-03-01T17:33:35.260 に答える
2

それが何をしているかを正確に確認する簡単な方法:

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

他の人が回答で言ったことに同意しますが、これにより、それが何をしているのかを正確に確認できます。:)

于 2011-03-01T17:40:48.000 に答える
2

前置演算子の構文は、次の前置表記に変換されます。

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

ここで、上記の A、B、および C として識別される 3 つの異なる関数呼び出しがあります。各呼び出しの引数は次のとおりです。

     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 の最初と 2 番目の引数を参照) は B の前に評価する必要があります。

コンパイラは最初の引数を評価しようとする前にすべての 2 番目の引数を実行することを決定できるため、これらの部分的な順序付けは呼び出しの実行に固有の要件をもたらしません。

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;

または...結合し続けます。

于 2011-03-01T18:04:08.793 に答える