2013-06-29 10 views
5

Der folgende Code erzeugt einen Dangling-Verweis, wie in der Warnung des Compilers und der Tatsache, dass der Destruktor für das Objekt A in der Funktion g() aufgerufen wird, bevor die Funktion zurückkehrt, angezeigt wird. Man kann auch überprüfen, dass in main() nach dem "Verwenden des Stapels" die zurückgegebene Referenz zumindest in einem Debug-Build Müll aufweist. Aber ich konnte das gleiche Verhalten in einem Release-Build nicht reproduzieren. Warum das? Welche Art von Optimierung macht der Compiler hier, um den Eindruck zu erwecken, dass die Referenz r Ok ist?Was passiert mit dem Snippet in einem Release-Build?

#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. Ich würde gegen NRVO argumentieren, da die Funktion kein A Objekt zurückgibt.

Edit: Als Antwort auf Mark Tolonen. Auch wenn ich diese Ausdrücke nach const A& r = g(1); das Release-Build enthalten nicht zeigt Müll in 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"; 
+2

Undefiniertes Verhalten ist undefiniert ... (Ausgänge 1 für mich in Freigabe und Debug) – jrok

+0

**** VS2010 **** – WaldB

+1

Überprüfung der Demontage. Es ist optimiert, entweder im Register oder verwenden Sie sofort. – Immueggpain

Antwort

1

Hier ist, was die geschwindigkeitsoptimierte (/ O2-Compiler-Schalter) Release-Build von Visual Studio 2012 64-Bit funktioniert tatsächlich, wenn es diesen Code ausgeführt wird und druckt ein ein:

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) 

Beachten Sie, dass es doesn‘ t sogar Mühe, das A Objekt wirklich zu verursachen und zu zerstören. Alles was es tut ist cout viermal anrufen. Jedes Mal, rdx hält das Objekt zu drucken. Die ersten drei drucken die Zeichenfolgen "Ctor \ n", "Dtor \ n" und "Verwenden des Stapels \ n". Der letzte sieht so aus, als ob er nur die ganze Zahl in edx druckt, die eine 1 ist.

Der Compiler kann wirklich alles für undefiniertes Verhalten tun. Es druckt etwas neben einem für den raumoptimierten (/ O1-Compiler-Schalter) oder, wie das OP herausgefunden hat, nicht-optimiert (/ Od).

+0

Brilliant !!!!!!!!!!!!!!! – WaldB

11

Es ist nur nicht definiertes Verhalten. Du bringst eine temporäre Referenz zurück, alles kann passieren.

Die A& g(int i) { A x(i); return x; } ist illegal.

Ein Debug-Build wird wahrscheinlich den Speicher löschen und Fehler verursachen, da der Speicher gelöscht wurde.

Ein Release-Build stört nicht. Du bezahlst für das, was du benutzt, oder? Es lässt den Speicher nur unberührt, markiert ihn jedoch als vom Betriebssystem wiederherstellbar. Alle Handschuhe sind danach ausgeschaltet.

Es ist eine (wohl) gute Sache, die mit dem VC++ - Compiler kommt. Sie werden sehen, was in Debug Build passiert, um Ihnen zu helfen ... nun ... besser zu debuggen. Nicht initialisierte Zeiger auf einen bestimmten Wert gesetzt, so dass Sie wissen es ist nicht initialisiert, Speicher Null nach einem , so dass Sie wissen es gelöscht wurde. Dies hilft bei der Früherkennung von Problemen, da Sie in einem Release-Build wahrscheinlich immer noch den Speicher sehen, wenn er nicht überschrieben wird, oder auf einen nicht initialisierten Zeiger zugreifen und zum Arbeiten erscheinen lassen usw. Probleme, die Sie sonst nicht sehen würden und zu der Zeit, die Sie entdecken würden, würde eine Menge Schaden verursachen und wäre sehr schwer zu diagnostizieren.

+2

@Elazar UB sagt * alles * darf passieren. Ich erklärte warum (zumindest versucht), aber es gibt wenig bis gar keinen Sinn darin, über UB nachzudenken. Vermeide es einfach. –

+0

Alles kann passieren, aber nichts Gutes :-) – user1764961

+0

Diese Erklärung ist, was ich gefragt habe. +1 – Elazar

Verwandte Themen