web-dev-qa-db-ja.com

C ++でのコンパイラと引数の評価順序

わかりました、標準ではC++実装が関数の引数を評価する順序を選択することを定めていることは承知していますが、実際にプログラムに影響を与えるシナリオでこれを実際に「活用」する実装はありますか?

古典的な例:

int i = 0;
foo(i++, i++);

注:評価の順序は信頼できないと言ってくれる人を探しているのではありません。私はそれをよく知っています。私が思うに、コンパイラーが実際に左から右の順序で評価するかどうかにのみ関心があります。なぜなら、それらが正しく実行されない場合、多くの不適切に記述されたコードが壊れるからです(正しくそうですが、おそらく文句を言うでしょう)。

57
RaptorFactor

引数のタイプ、呼び出された関数の呼び出し規約、アーキテクチャー、コンパイラーによって異なります。 x86では、 Pascal 呼び出し規則は引数を左から右に評価しますが、C呼び出し規則では( __ cdecl )は右から左です。複数のプラットフォームで実行されるほとんどのプログラムは、呼び出し規約を考慮に入れて、驚きをスキップします。

興味があれば、Raymond Chenのブログにニース 記事 があります。 GCCマニュアルの Stack and Calling セクションを参照することもできます。

編集:髪を分ける限り:私の答えはこれを言語の質問としてではなく、プラットフォームの質問として扱います。言語標準はどちらか一方を保証したり優先したりせず、unspecifiedのままにします。表現に注意してください。これが未定義であるとは言いません。この意味で未指定とは、信頼できない、移植できない動作を意味します。私は便利なC仕様/ドラフトを持っていませんが、それは私のn2798ドラフト(C++)からのものと同様である必要があります

抽象マシンの特定の他の側面と操作は、この国際標準では指定されていません(たとえば、関数の引数の評価順序)。可能であれば、この国際標準は一連の許容可能な動作を定義します。これらは、抽象マシンの非決定的な側面を定義します。したがって、抽象マシンのインスタンスは、特定のプログラムおよび特定の入力に対して複数の実行シーケンスを持つことができます。

51
dirkgently

c ++の標準 で答えを見つけました。

5.2.2.8項:

引数の評価順序は不定です。引数式評価のすべての副作用は、関数に入る前に有効になります。後置式と引数式リストの評価の順序は指定されていません。

つまり、コンパイラのみに依存します。

7
Igor

これを読む

それはあなたの質問の正確なコピーではありませんが、私の回答(および他のいくつか)はあなたの質問もカバーしています。

コンパイラーが右から左に選択するだけでなく、それらをインターリーブする可能性があるのには、非常に優れた最適化の理由があります。

この規格では、順次の順序付けも保証されていません。それonlyは、関数が呼び出されたときに、すべての引数が完全に評価されていることを保証します。

そして、はい、GCCのいくつかのバージョンがまさにこれを実行するのを見てきました。あなたの例では、foo(0,0)が呼び出され、後でiは2になります。 (コンパイラーの正確なバージョン番号をお知らせすることはできません。少し前のことですが、この動作が再び表示されるのに驚かないでしょう。これは、命令をスケジュールする効率的な方法です)

6
jalf

すべての引数が評価されます。順序が定義されていません(標準に従って)。しかし、C/C++のすべての実装(私が知っている)は、関数の引数を右から左から評価します。 編集:CLangは例外です(以下のコメントを参照してください)。

右から左への評価順序は非常に古いものだと思います(最初のCコンパイラ以降)。 C++が発明される前の確かな方法であり、初期のC++実装は単にCに変換されただけなので、C++のほとんどの実装は同じ評価順序を維持します。

関数の引数を右から左に評価する技術的な理由がいくつかあります。スタックアーキテクチャでは、引数は通常スタックにプッシュされます。 C/C++では、実際に指定されたよりも多くの引数を使用して関数を呼び出すことができます-余分な引数は同様に無視されます。引数が左から右に評価され、左から右にプッシュされる場合、スタックポインターのすぐ下のスタックスロットは最後の引数を保持し、関数が特定の引数のオフセットに到達する方法はありません(プッシュされる引数の実際の数は呼び出し元に依存するため)。

右から左へのプッシュ順序では、スタックポインターのすぐ下のスタックスロットは常に最初の引数を保持し、次のスロットは2番目の引数を保持します。引数のオフセットは常に関数に対して決定的です(これは、呼び出される場所とは別の場所でライブラリにコンパイルされます)。

現在、右から左へのプッシュ順序は右から左への評価順序を義務付けていませんが、初期のコンパイラではメモリが不足しています。右から左への評価順序で、同じスタックをインプレースで使用できます(基本的に、引数を評価した後-これは式または関数呼び出しである可能性があります!-戻り値はすでに正しい位置にありますスタック)。左から右への評価では、引数値を個別に保存し、逆の順序でスタックにプッシュバックする必要があります。

5
Stephen Chung

最後に、2007年にx86ハードウェアでVS2005とGCC 3.xの違いが見られたのは、その可能性が非常に高い(そうでしたか?)そのため、評価の順序に依存することはもうありません。多分それは今より良いです。

2
Robert Gould

C++標準で独立性が要求され、相互依存性がないため、ほとんどの最新のコンパイラーは、引数を計算する命令をインターリーブしようとするでしょう。これを行うと、深くパイプライン化されたCPUの実行ユニットをフルに保ち、スループットを向上させるのに役立ちます。 (少なくとも、最適化フラグが指定されている場合は、最適化コンパイラーであると主張するコンパイラーがそうすることを期待します。)

2
j_random_hacker