5

以下のコードは、コンパイラによって出力された警告と、関数内のAオブジェクトのデストラクタg()が関数が戻る前に呼び出されるという事実に見られるように、ダングリング参照を生成します。「スタックを使用」した後、返された参照にガベージがあることを確認することもできmain()ます (少なくともデバッグ ビルドでは)。しかし、リリース ビルドでは同じ動作を再現できませんでした。何故ですか?r参照が OKであるという印象を与えるために、コンパイラはここでどのような最適化を行っているのでしょうか?

#include <iostream>

struct A{
    A(int i) : i(i) { std::cout << "Ctor\n"; }
    A(const A& a) { i = a.i; std::cout << "Copy ctor\n"; }
    ~A() { std::cout << "Dtor\n"; }
    int i;
};

A& g(int i) { A x(i); return x; }

int main()
{
    const A& r = g(1);
    std::cout << "Using the stack\n";     
    std::cout << r.i << '\n';   // r.i has garbage in debug, but not in a release build.
}

PS。関数はオブジェクトを返さないため、NRVO には反対しAます。

編集:マーク・トロネンへの返信。リリースビルド後にこれらの式を含めても、const A& r = g(1);ゴミは表示されませんstd::cout << r.i << '\n';

std::cout << "Using the stack ...................................................................................................................\n";
std::cout << "Using the stack ...................................................................................................................\n";
std::cout << "Using the stack ...................................................................................................................\n";
std::cout << "Using the stack ...................................................................................................................\n";
4

2 に答える 2

11

それは未定義の動作です。参照によって一時を返すと、何でも起こります。

A& g(int i) { A x(i); return x; }違法です。

メモリがクリアされたため、デバッグ ビルドによってメモリがクリアされ、エラーが発生する可能性があります。

リリース ビルドは気にしません。使用した分だけお支払いいただきますよね?メモリはそのまま残しますが、OS によって再利用可能としてマークされます。その後、すべての手袋を外します。

これは、VC++ コンパイラに付属している (ほぼ間違いなく) 良いことです。デバッグ ビルドでさまざまなことが起こっていることがわかります。初期化されていないポインターが特定の値に設定されているため、初期化されていないことがわかり、メモリが削除されたdeleteことがわかります。これは、問題をより早く特定するのに役立ちます。なぜなら、リリース ビルドでは、メモリが上書きされていない場合でもメモリが表示されるか、初期化されていないポインタにアクセスして動作しているように見えるなどの理由からです。そうでなければ表示されない問題、および発見した時点で多くの害を引き起こし、診断が非常に困難になります。

于 2013-06-29T21:38:45.557 に答える
1

Visual Studio 2012 64 ビットの速度が最適化された (/O2 コンパイラ スイッチ) リリース ビルドが、このコードを実行してコードを出力したときに実際に行うことは次のとおりです。

int main()
{
000000013F7C7E50  sub         rsp,28h  
    const A& r = g(1);
000000013F7C7E54  lea         rdx,[string "Ctor\n" (013F83DA4Ch)]  
000000013F7C7E5B  lea         rcx,[std::cout (013F85FAA0h)]  
000000013F7C7E62  call        std::operator<<<std::char_traits<char> > (013F7C1500h)  
000000013F7C7E67  lea         rdx,[string "Dtor\n" (013F83DA54h)]  
000000013F7C7E6E  lea         rcx,[std::cout (013F85FAA0h)]  
000000013F7C7E75  call        std::operator<<<std::char_traits<char> > (013F7C1500h)  
    std::cout << "Using the stack\n";     
000000013F7C7E7A  lea         rdx,[string "Using the stack\n" (013F83DA60h)]  
000000013F7C7E81  lea         rcx,[std::cout (013F85FAA0h)]  
000000013F7C7E88  call        std::operator<<<std::char_traits<char> > (013F7C1500h)  
    std::cout << r.i << '\n';   // r.i has garbage in debug, but not in a release build.
000000013F7C7E8D  lea         rcx,[std::cout (013F85FAA0h)]  
000000013F7C7E94  mov         edx,1  
000000013F7C7E99  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (013F7C1384h)  
000000013F7C7E9E  mov         dl,0Ah  
000000013F7C7EA0  mov         rcx,rax  
000000013F7C7EA3  call        std::operator<<<std::char_traits<char> > (013F7C10EBh)  

オブジェクトを実際に作成および破棄することさえ気にしないことに注意してくださいAcout4 回呼び出すだけです。毎回、rdx印刷するオブジェクトを保持します。最初の 3 つは、文字列 "Ctor\n"、"Dtor\n"、および "Using the stack\n" を出力します。edx最後のものは、整数を出力するだけのように見えます1.

コンパイラは、未定義の動作に対して本当に何でもできます。スペース最適化 (/O1 コンパイラ スイッチ)、または最適化されていない OP として (/Od) を出力します。

于 2013-06-30T00:50:02.497 に答える