web-dev-qa-db-ja.com

C ++が関数の戻り値からstd :: vectorに要素を格納するときの予期しない結果

関数に再割り当てが含まれる場合、一部のコンパイラは関数呼び出しの前にアドレスを保存することがあります。無効なアドレスに格納された戻り値を導きます。

上記の説明で動作を説明する例があります。

#include <stdio.h>
#include <vector> 
using namespace std;

vector<int> A; 
int func() { 
    A.Push_back(3);
    A.Push_back(4);
    return 5; 
} 
int main() { 
    A.reserve(2);
    A.Push_back(0);
    A.Push_back(1);
    A[1] = func();
    printf("%d\n", A[1]);
    return 0;
}

一般的なC++コンパイラがいくつかあり、テスト結果は次のとおりです。

  • GCC(GNU Compiler Collection):ランタイムエラーまたは出力1
  • Clang:出力5
  • VC++:出力5

未定義の動作ですか?

45
Morris Yang

この動作は、C++ 17より前のすべてのC++バージョンで未定義です。単純な理由は、代入演算子の両側が任意の順序で評価できることです。

  • _A[1]_が最初に評価されると仮定すると、その時点でAの2番目の要素を参照する_int&_を取得します。
  • 次に、func()が評価されます。これにより、以前に取得した_int&_をダングリング参照のままにして、ベクトル用のストレージを再割り当てできます。
  • 最後に、割り当てが実行され、未割り当てのストレージに書き込まれます。標準のアロケータはメモリをキャッシュするため、OSは多くの場合このエラーをキャッチしません。

C++ 17でのみ、割り当ての 特別規則2 が作成されました。

すべての単純な割り当て式E1 = E2およびすべての複合割り当て式E1 @ = E2では、E2のすべての値の計算と副作用は、E1のすべての値の計算と副作用の前にシーケンスされます

C++ 17では、func()の呼び出し後に_A[1]_を評価する必要があります。これにより、定義済みの信頼できる動作が提供されます。

51
Ulrich Eckhardt