Während einer Diskussion hatte ich mit ein paar Kollegen neulich ein Stück Code in C++ zusammen, um eine Speicherzugriffsverletzung zu veranschaulichen.Wird die Inkonsistenz der Compiler-Optimierung vollständig durch undefiniertes Verhalten erklärt?
Ich bin gerade dabei, langsam nach C++ zurückzukehren, nachdem ich fast ausschließlich Sprachen mit Garbage Collection benutzt habe, und ich denke, mein Touchverlust zeigt, da ich durch das Verhalten meiner Short ziemlich verwirrt bin Programm ausgestellt.
Der Code in Frage wie zum Beispiel:
#include <iostream>
using std::cout;
using std::endl;
struct A
{
int value;
};
void f()
{
A* pa; // Uninitialized pointer
cout<< pa << endl;
pa->value = 42; // Writing via an uninitialized pointer
}
int main(int argc, char** argv)
{
f();
cout<< "Returned to main()" << endl;
return 0;
}
I mit GCC 4.9.2 auf Ubuntu 15.04 mit -O2
Compiler-Flag Set zusammengestellt. Meine Erwartungen beim Ausführen waren, dass es abstürzen würde, wenn die Zeile, die durch meinen Kommentar als "Schreiben über einen nicht initialisierten Zeiger" bezeichnet wurde, ausgeführt wurde.
Entgegen meiner Erwartungen jedoch lief das Programm erfolgreich zu Ende, produziert die folgende Ausgabe:
0
Returned to main()
ich den Code mit einem -O0
Flag neu kompiliert (um alle Optimierungen zu deaktivieren) und lief das Programm erneut . Dieses Mal war das Verhalten wie ich erwartet hatte:
0
Segmentation fault
(Na ja, fast: Ich kann nicht ein Zeiger werden initialisiert auf 0 erwartet hatte) Basierend auf dieser Beobachtung, nehme ich an, dass beim Kompilieren mit -O2
Satz, der fatale Befehl wurde weg optimiert. Dies ist sinnvoll, da kein weiterer Code auf den pa->value
zugreift, nachdem er von der fehlerhaften Zeile gesetzt wurde, so dass der Compiler vermutlich festgestellt hat, dass seine Entfernung das beobachtbare Verhalten des Programms nicht verändern würde.
Ich reproduzierte dies mehrmals und jedes Mal, wenn das Programm abstürzen würde, wenn ohne Optimierung kompiliert und wunderbar funktionieren, wenn mit -O2
kompiliert.
wurde Meine Hypothese weiter bestätigt, wenn ich eine Zeile hinzugefügt, die die pa->value
ausgibt, bis zum Ende des f()
‚s Körpers:
cout<< pa->value << endl;
Genau wie bei dieser Linie an Ort und Stelle zu erwarten, stürzt das Programm, konsequent , unabhängig von der Optimierungsstufe, mit der es kompiliert wurde.
Das alles macht Sinn, wenn meine Annahmen soweit stimmen. Allerdings , wo mein Verständnis etwas bricht, ist für den Fall, wo ich den Code aus dem Körper von f()
direkt an main()
bewegen, etwa so:
int main(int argc, char** argv)
{
A* pa;
cout<< pa << endl;
pa->value = 42;
cout<< pa->value << endl;
return 0;
}
Mit Optimierungen deaktiviert, dieses Programm stürzt ab, wie erwartet. Mit -O2
jedoch läuft das Programm erfolgreich zu Ende und erzeugt die folgende Ausgabe:
0
42
Und das macht keinen Sinn für mich.
This answer erwähnt "Dereferenzierung eines Zeigers, der noch nicht definitiv initialisiert wurde", was genau ich mache, als eine der Quellen von undefiniertem Verhalten in C++.
So ist dieser Unterschied in der Art und Weise Optimierung wirkt sich auf den Code in main()
, verglichen mit dem Code in f()
, vollständig durch die Tatsache erklärt, dass mein Programm enthält UB und damit Compiler ist technisch frei zu „ausrasten“, oder Gibt es einen grundlegenden Unterschied, von dem ich nicht weiß, ob der Code in main()
optimiert ist, verglichen mit Code in anderen Routinen?
Die beiden Alternativen in Ihrem letzten Absatz schließen sich nicht gegenseitig aus. –
Wenn UB beteiligt ist, können Sie nicht erwarten, dass der C++ - Standard das resultierende Programm und sein Ergebnis beschreibt. ("... stellt keine Anforderungen") – milleniumbug
@BenjaminLindley Nun, ich sagte "oder", nicht "xor". :) – TerraPass