Nehmen wir an, ich kompiliere schlecht geschriebenen C ++ - Quellcode, der undefiniertes Verhalten hervorruft, und daher (wie sie sagen) "kann alles passieren".
Aus der Perspektive dessen, was die C ++ - Sprachspezifikation in einem "konformen" Compiler als akzeptabel erachtet, bedeutet "alles" in diesem Szenario, dass der Compiler abstürzt (oder meine Passwörter stiehlt oder sich auf andere Weise beim Kompilieren schlecht verhält oder fehlerhaft ist) oder ist Der Umfang des undefinierten Verhaltens beschränkt sich speziell darauf, was passieren kann, wenn die resultierende ausführbare Datei ausgeführt wird.
c++
language-lawyer
undefined-behavior
Jeremy Friesner
quelle
quelle
Antworten:
Die normative Definition von undefiniertem Verhalten lautet wie folgt:
Obwohl die Notiz selbst nicht normativ ist, beschreibt sie eine Reihe von Verhaltensweisen, von denen bekannt ist, dass sie Implementierungen aufweisen. Ein Absturz des Compilers (bei dem die Übersetzung abrupt beendet wird) ist laut diesem Hinweis legitim. Aber wirklich, wie der normative Text sagt, setzt der Standard weder der Ausführung noch der Übersetzung Grenzen. Wenn eine Implementierung Ihre Passwörter stiehlt, verstößt dies nicht gegen einen im Standard festgelegten Vertrag.
quelle
Die meisten Arten von UB, über die wir uns normalerweise Sorgen machen, wie NULL-Deref oder Division durch Null, sind Laufzeit- UB. Das Kompilieren einer Funktion, die bei Ausführung zur Laufzeit UB führen würde, darf nicht zum Absturz des Compilers führen. Es sei denn, es kann vielleicht beweisen, dass die Funktion (und dieser Pfad durch die Funktion) definitiv wird durch das Programm ausgeführt werden.
(2. Gedanke: Vielleicht habe ich beim Kompilieren nicht berücksichtigt, dass Template / Constexpr eine Evaluierung erforderlich macht. Möglicherweise darf UB während der Übersetzung willkürliche Verrücktheiten verursachen, selbst wenn die resultierende Funktion niemals aufgerufen wird.)
Das Verhalten während der Übersetzung des ISO C ++ - Zitats in der Antwort von @ StoryTeller ähnelt der im ISO C-Standard verwendeten Sprache. C enthält keine Vorlagen oder
constexpr
obligatorische Auswertungen zur Kompilierungszeit.Aber lustige Tatsache : ISO C sagt in einem Hinweis, dass die Beendigung einer Übersetzung mit einer Diagnosemeldung erfolgen muss. Oder "Verhalten während der Übersetzung ... auf dokumentierte Weise". Ich denke nicht, dass "die Situation vollständig ignorieren" so verstanden werden könnte, dass die Übersetzung gestoppt wird.
Alte Antwort, geschrieben, bevor ich etwas über die Übersetzungszeit von UB erfuhr. Dies gilt jedoch für Runtime-UB und ist daher möglicherweise immer noch nützlich.
Es gibt keine UB, die zur Kompilierungszeit passiert . Es kann für den Compiler entlang eines bestimmten Ausführungspfads sichtbar sein , aber in C ++ ist dies nicht geschehen erst wenn die Ausführung diesen Ausführungspfad über eine Funktion erreicht hat.
Fehler in einem Programm, die das Kompilieren unmöglich machen, sind nicht UB, sondern Syntaxfehler. Ein solches Programm ist in der C ++ - Terminologie "nicht wohlgeformt" (wenn ich meine Standardsprache korrekt habe). Ein Programm kann wohlgeformt sein, aber UB enthalten. Unterschied zwischen undefiniertem Verhalten und schlecht geformt, keine Diagnosemeldung erforderlich
Wenn ich nichts falsch verstehe, muss dieses Programm in ISO C ++ korrekt kompiliert und ausgeführt werden, da die Ausführung niemals die Division durch Null erreicht. (In der Praxis ( Godbolt ) machen gute Compiler nur funktionierende ausführbare Dateien. Gcc / clang warnt davor,
x / 0
aber nicht davor , selbst wenn sie optimiert werden. Trotzdem versuchen wir zu sagen, wie niedrig ISO C ++ die Qualität der Implementierung sein lässt. Überprüfen Sie also gcc / clang ist kaum ein nützlicher Test, außer um zu bestätigen, dass ich das Programm richtig geschrieben habe.)Ein Anwendungsfall hierfür könnte der C-Präprozessor oder
constexpr
Variablen und die Verzweigung dieser Variablen sein, was zu Unsinn in einigen Pfaden führt, die für diese Auswahl von Konstanten nie erreicht werden.Es kann davon ausgegangen werden, dass Ausführungspfade, die eine zur Kompilierungszeit sichtbare UB verursachen, niemals verwendet werden, z. B. könnte ein Compiler für x86 eine
ud2
(Ursache für eine Anweisung für unzulässige Anweisungen) als Definition für ausgebencause_UB()
. Oder innerhalb einer Funktion kann der Zweig entfernt werden , wenn eine Seite vonif()
zu nachweisbarem UB führt .Aber der Compiler muss immer noch alles andere auf vernünftige und korrekte Weise kompilieren . Alle Pfade, die UB nicht begegnen (oder deren Nachweis nicht nachgewiesen werden kann), müssen weiterhin zu asm kompiliert werden, das so ausgeführt wird, als ob die abstrakte C ++ - Maschine es ausführen würde.
Sie könnten argumentieren, dass die bedingungslose UB in der Kompilierungszeit
main
eine Ausnahme von dieser Regel darstellt. Oder auf andere Weise zur Kompilierungszeit nachweisbar, dass die Ausführung ab beginntmain
tatsächlich die garantierte UB erreicht.Ich würde immer noch argumentieren, dass das Verhalten von legalen Compilern das Produzieren einer Granate beinhaltet, die explodiert, wenn sie ausgeführt wird. Oder plausibler, eine Definition
main
davon besteht aus einer einzigen illegalen Anweisung. Ich würde behaupten, wenn Sie das Programm nie ausführen, gibt es noch keine UB. Der Compiler selbst darf nicht explodieren, IMO.Funktionen, die mögliche oder nachweisbare UB innerhalb von Zweigen enthalten
UB auf einem bestimmten Ausführungspfad reicht rechtzeitig zurück, um den gesamten vorherigen Code zu "kontaminieren". In der Praxis können Compiler diese Regel jedoch nur dann nutzen, wenn sie tatsächlich nachweisen können , dass Ausführungspfade zu UB führen, die zur Kompilierungszeit sichtbar sind. z.B
Der Compiler muss asm erstellen, das für alle
x
anderen als 3 funktioniert , bis zu den Punkten, an denenx * 5
bei INT_MIN und INT_MAX ein UB mit Vorzeichenüberlauf verursacht wird. Wenn diese Funktion nie mit aufgerufen wirdx==3
, enthält das Programm natürlich keine UB und muss wie geschrieben funktionieren.Wir hätten genauso gut
if(x == 3) __builtin_unreachable();
in GNU C schreiben können , um dem Compiler mitzuteilen, dass diesx
definitiv nicht 3 ist.In der Praxis gibt es in normalen Programmen überall "Minenfeld" -Code. Beispielsweise verspricht jede Division durch eine Ganzzahl dem Compiler, dass sie nicht Null ist. Jeder Zeiger-Deref verspricht dem Compiler, dass er nicht NULL ist.
quelle
Was bedeutet "legal" hier? Alles, was nicht dem C-Standard oder dem C ++ - Standard widerspricht, ist gemäß diesen Standards legal. Wenn Sie eine Anweisung ausführen
i = i++;
und infolgedessen Dinosaurier die Welt übernehmen, widerspricht dies nicht den Standards. Es widerspricht jedoch den Gesetzen der Physik, also wird es nicht passieren :-)Wenn undefiniertes Verhalten Ihren Compiler zum Absturz bringt, verstößt dies nicht gegen den C- oder C ++ - Standard. Dies bedeutet jedoch, dass die Qualität des Compilers verbessert werden könnte (und wahrscheinlich sollte).
In früheren Versionen des C-Standards gab es Anweisungen, die fehlerhaft waren oder nicht von undefiniertem Verhalten abhingen:
Das Zuweisen einer Konstanten 0 zu einem Zeichen * ist zulässig. Das Zulassen einer Nicht-Null-Konstante ist nicht. Da der Wert 1/0 ein undefiniertes Verhalten ist, ist es ein undefiniertes Verhalten, ob der Compiler diese Anweisung akzeptieren soll oder nicht. (Heutzutage entspricht 1/0 nicht mehr der Definition des "ganzzahligen konstanten Ausdrucks").
quelle