0

Visual C++ Express 2010 を使用して C++ プロジェクトに取り組んでいるときに、理解したい興味深い問題を見つけました。問題は、デバッグ モードまたはリリース モードでコンパイルすると、プログラムの結果が異なることです。それを再現するための小さなプログラムを作成しました。

#include <stdio.h>


int firstarg(int i)
{
    printf("First argument called with i = %d\n", i);

    return i;
}

int secondarg(int i)
{
    printf("Second argument called with i = %d\n", i);

    return i;
}

void function(int i, int j)
{
    printf("Function called with %d, %d\n", i,j);
}

int main(int argc, char* argv[])
{

    // Line with the problem!
    for (int i = 0; i < 5; ) function(firstarg(i), secondarg(i++)); 

    return 0;
}

// Result on RELEASE:
Second argument called with i = 0
First argument called with i = 0
Function called with 0, 0
Second argument called with i = 1
First argument called with i = 1
Function called with 1, 1
Second argument called with i = 2
First argument called with i = 2
Function called with 2, 2
Second argument called with i = 3
First argument called with i = 3
Function called with 3, 3
Second argument called with i = 4
First argument called with i = 4
Function called with 4, 4

// Result on DEBUG
Second argument called with i = 0
First argument called with i = 1
Function called with 1, 0
Second argument called with i = 1
First argument called with i = 2
Function called with 2, 1
Second argument called with i = 2
First argument called with i = 3
Function called with 3, 2
Second argument called with i = 3
First argument called with i = 4
Function called with 4, 3
Second argument called with i = 4
First argument called with i = 5
Function called with 5, 4

ご覧のとおり、どちらの場合も、2 番目の引数は最初の引数の前に評価されます (引数が何らかの LIFO スタックで処理される場合、私が期待したもの)。ただし、リリースでは、変数 i のインクリメントは「最適化されて」、ループの次の反復まで遅延されます。それは予想外だったので、何が起こっているのか本当に知りたいです。

もちろん、ループを次のように変更することで、コードを簡単に「修正」できます。

    for (int i = 0; i < 5; ++i) function(firstarg(i+1), secondarg(i)); 

コンパイルパラメータに関係なく、常に同じ結果が得られます。それでも、このインクリメントの最適化の背後にある理由を本当に理解したいと思っています。

PS。ちなみに、Linux の gcc ではこの問題を再現できませんでした (-O0 フラグでデバッグ、-O3 フラグでリリース)。

4

3 に答える 3

3

あなたは結果を誤解しています。インクリメントは、ループの次の反復まで「最適化」または遅延されません。i次の反復の前にの値を確認する方法がありません。これを試して:

for (int i = 0; i < 5; )
{
   function(firstarg(i), secondarg(i++)); 
   printf("At end of iteration, i = %d\n");
}

そして、まったく遅れていないことがわかります。

シーケンスポイントを介在させずに変数にアクセスし、同じ変数を変更できるため、コードはUBです。非常にクレイジーな結果が得られる可能性がありますが、実際には、最適化に応じて、「期待される」結果の両方が得られます。関数パラメーターが評価される順序は規定されていません。

于 2012-09-03T09:51:12.950 に答える
2

プログラムはi、介在するシーケンス ポイント ( の関数呼び出し式内function) なしで、変数を 2 回変更して読み取ります。これは、単に未定義の動作です。

于 2012-09-03T09:51:48.143 に答える
0

未定義の動作につながるシーケンスポイントの問題があると思います:

いずれにせよ、解決策はこの種のコードを書かないことです。

于 2012-09-03T09:51:23.883 に答える