8

Consider this code:

#include <iostream>

struct Test
{
    int x;
    int y;
};

Test func(const Test& in)
{
    Test out;
    out.x=in.y;
    out.y=in.x;
    return out;
}

int main()
{
    Test test{1,2};
    std::cout << "x: " << test.x << ", y: " << test.y << "\n";
    test=func(test);
    std::cout << "x: " << test.x << ", y: " << test.y << "\n";
}

One would expect an output like this:

x: 1, y: 2
x: 2, y: 1

and this is indeed what I get. But due to copy elision, could out be in the same place in memory as in and result in the last line of output being x: 2, y: 2?

I've tried compiling with gcc and clang with both -O0 and -O3, and the results still look as intended.

4

5 に答える 5

3

いいえ、できませんでした。

最適化とは、適切に作成された (悪条件ではない) コードで未定義の動作が発生することを意味するものではありません。

この参照を確認してください:

準拠する実装は、以下で説明するように、抽象マシンの観察可能な動作を (のみ) エミュレートする必要があります。...

整形式プログラムを実行する適合実装は、同じプログラムと同じ入力を持つ抽象マシンの対応するインスタンスの可能な実行シーケンスの 1 つと同じ観測可能な動作を生成するものとします。...

抽象マシンの観察可能な動作は、揮発性データへの読み取りと書き込み、およびライブラリ I/O 関数の呼び出しのシーケンスです。...

この回答から取得。

この回答では、コピー省略が異なる出力を生成する場合があることがわかります!

于 2015-10-23T14:52:13.740 に答える
3

これは整形式のコードです。as -if ルールに違反するため、最適化によって整形式のコードが壊れることはありません。これは、次のことを示しています。

特に、抽象マシンの構造をコピーまたはエミュレートする必要はありません。むしろ、以下で説明するように、抽象マシンの観察可能な動作をエミュレートする(のみ)ために、適合する実装が必要です。

コピーの省略を除いて:

[...]実装では、コピー/移動操作用に選択されたコンストラクターおよび/またはオブジェクトのデストラクタに副作用がある場合でも、クラス オブジェクトのコピー/移動構築を省略することができます。[...]

しかし、順序付け規則は従う必要があり、ドラフト標準に進むと、セクション 5.17 から左オペランドと右オペランドが評価された後に代入が順序付けられることがわかります。

いずれの場合も、代入は、右オペランドと左オペランドの値の計算の後、代入式の値の計算の前に順序付けされます。

また、関数の本体は、セクション 1.9 の関数呼び出しで明確に順序付けされていない他の評価に対して不定に順序付けられていることがわかっています。

呼び出された関数の本体の実行の前後に特に順序付けされていない呼び出し側関数 (他の関数呼び出しを含む) 内のすべての評価は、呼び出された関数の実行に関して不定に順序付けられます。

不確定な順序付けとは、次のことを意味します。

評価 A と B は、A が B の前にシーケンスされるか、B が A の前にシーケンスされる場合、不定にシーケンスされますが、どちらが指定されていません。[ 注: 不確定な順序の評価はオーバーラップできませんが、どちらかが最初に実行される可能性があります。—終わりのメモ]

于 2015-10-23T15:00:37.550 に答える
1

Elision は、オブジェクトのライフタイムと ID のマージです。

省略は、一時 (無名オブジェクト) とそれが (直接) 構築するために使用される名前付きオブジェクトの間、および関数の引数ではない関数ローカル変数と関数の戻り値の間で発生する可能性があります。

事実上、エリシオンは通勤します。(オブジェクト A と B が一緒に省略され、B と C が一緒に省略された場合、実際には A と C も一緒に省略されます)。

関数の戻り値を関数外の変数で省略するには、戻り値からその戻り値を直接構築する必要があります。一部のコンテキストでは、構築された変数は構築される前に名前を付けることができますが、(上記のコードと同様の方法で) それを使用すると、コンストラクターが発生する前の未定義の動作になります。

この外部変数のコンストラクターは、本体の後に配列されるfuncため、 afterfuncが呼び出されます。したがって、呼び出される前に発生することはありませんでしたfunc

これは、構築される前に変数に名前を付け、それを に渡しfunc、変数を の戻り値で初期化する場合のfuncです。この場合、コンパイラは省略しないことを選択したように見えますが、以下のコメントで見られるように、id は実際に削除しました: 私の UB の呼び出しは省略を隠しました。(畳み込みは、コンパイラが test.x と test.y の値を事前に計算するのを防ぐ試みでした)。

于 2015-10-23T19:20:55.397 に答える