Undefiniertes, nicht spezifiziertes und implementierungsdefiniertes Verhalten

530

Was ist undefiniertes Verhalten in C und C ++? Was ist mit nicht spezifiziertem Verhalten und implementierungsdefiniertem Verhalten? Was ist der Unterschied zwischen ihnen?

Zolomon
quelle
1
Ich war mir ziemlich sicher, dass wir das vorher gemacht haben, aber ich kann es nicht finden. Siehe auch: stackoverflow.com/questions/2301372/…
dmckee --- Ex-Moderator Kätzchen
1
Aus den häufig gestellten Fragen zu comp.lang.c: Es scheint
Jamesdlin
1
Hier ist eine interessante Diskussion (der Abschnitt "Anhang L und undefiniertes Verhalten").
Owen

Antworten:

406

Undefiniertes Verhalten ist einer der Aspekte der C- und C ++ - Sprache, die für Programmierer aus anderen Sprachen überraschend sein können (andere Sprachen versuchen, es besser zu verbergen). Grundsätzlich ist es möglich, C ++ - Programme zu schreiben, die sich nicht vorhersehbar verhalten, obwohl viele C ++ - Compiler keine Fehler im Programm melden!

Schauen wir uns ein klassisches Beispiel an:

#include <iostream>

int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

Die Variable pzeigt auf das Zeichenfolgenliteral "hello!\n", und die beiden folgenden Zuweisungen versuchen, dieses Zeichenfolgenliteral zu ändern. Was macht dieses Programm? Gemäß Abschnitt 2.14.5 Absatz 11 des C ++ - Standards wird undefiniertes Verhalten aufgerufen :

Der Versuch, ein Zeichenfolgenliteral zu ändern, ist nicht definiert.

Ich kann Leute schreien hören "Aber warte, ich kann das problemlos kompilieren und die Ausgabe erhalten yellow" oder "Was meinst du mit undefinierten String-Literalen, die im Nur-Lese-Speicher gespeichert sind, sodass der erste Zuweisungsversuch zu einem Core-Dump führt". Dies ist genau das Problem mit undefiniertem Verhalten. Grundsätzlich lässt der Standard zu, dass alles passiert, wenn Sie undefiniertes Verhalten aufrufen (sogar Nasendämonen). Wenn es ein "korrektes" Verhalten gemäß Ihrem mentalen Modell der Sprache gibt, ist dieses Modell einfach falsch; Der C ++ - Standard hat die einzige Abstimmung, Punkt.

Andere Beispiele für undefiniertes Verhalten sind der Zugriff auf ein Array außerhalb seiner Grenzen, die Dereferenzierung des Nullzeigers , der Zugriff auf Objekte nach Ablauf ihrer Lebensdauer oder das Schreiben von angeblich cleveren Ausdrücken wie i++ + ++i.

In Abschnitt 1.9 des C ++ - Standards werden auch die beiden weniger gefährlichen Brüder des undefinierten Verhaltens erwähnt, das nicht spezifizierte Verhalten und das implementierungsdefinierte Verhalten :

Die semantischen Beschreibungen in dieser Internationalen Norm definieren eine parametrisierte nichtdeterministische abstrakte Maschine.

Bestimmte Aspekte und Operationen der abstrakten Maschine werden in dieser Internationalen Norm als implementierungsdefiniert beschrieben (z. B. sizeof(int)). Diese bilden die Parameter der abstrakten Maschine. Jede Implementierung muss eine Dokumentation enthalten, in der ihre Merkmale und ihr Verhalten in dieser Hinsicht beschrieben werden.

Bestimmte andere Aspekte und Operationen der abstrakten Maschine werden in dieser Internationalen Norm als nicht spezifiziert beschrieben (z. B. Reihenfolge der Bewertung von Argumenten für eine Funktion). Nach Möglichkeit definiert diese Internationale Norm eine Reihe zulässiger Verhaltensweisen. Diese definieren die nichtdeterministischen Aspekte der abstrakten Maschine.

Bestimmte andere Operationen werden in dieser Internationalen Norm als undefiniert beschrieben (z. B. der Effekt der Dereferenzierung des Nullzeigers). [ Hinweis : Diese Internationale Norm stellt keine Anforderungen an das Verhalten von Programmen, die undefiniertes Verhalten enthalten. - Endnote ]

Im Einzelnen heißt es in Abschnitt 1.3.24:

Das zulässige undefinierte Verhalten reicht vom vollständigen Ignorieren der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer für die Umgebung charakteristischen dokumentierten Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit der Ausgabe) einer Diagnosemeldung).

Was können Sie tun, um undefiniertes Verhalten zu vermeiden? Grundsätzlich muss man gute C ++ - Bücher von Autoren lesen, die wissen, wovon sie sprechen. Schrauben Sie Internet-Tutorials. Bullschildt schrauben.

Fredoverflow
quelle
6
Es ist eine seltsame Tatsache, die sich aus der Zusammenführung ergibt, dass diese Antwort nur C ++ abdeckt, die Tags dieser Frage jedoch C enthalten. C hat einen anderen Begriff von "undefiniertem Verhalten": Die Implementierung muss weiterhin Diagnosemeldungen geben, auch wenn das Verhalten ebenfalls angegeben wird für bestimmte Regelverstöße (Einschränkungsverletzungen) undefiniert sein.
Johannes Schaub - Litb
8
@ Benoit Es ist undefiniertes Verhalten, weil der Standard sagt, dass es undefiniertes Verhalten ist, Punkt. Auf einigen Systemen werden Zeichenfolgenliterale tatsächlich im schreibgeschützten Textsegment gespeichert, und das Programm stürzt ab, wenn Sie versuchen, ein Zeichenfolgenliteral zu ändern. Auf anderen Systemen wird das Zeichenfolgenliteral tatsächlich als Änderung angezeigt. Der Standard schreibt nicht vor, was geschehen muss. Das bedeutet undefiniertes Verhalten.
Fredoverflow
5
@FredOverflow, Warum erlaubt uns ein guter Compiler, Code zu kompilieren, der undefiniertes Verhalten liefert? Genau das, was gut kann diese Art von Code geben Kompilieren? Warum haben uns nicht alle guten Compiler ein großes rotes Warnzeichen gegeben, wenn wir versuchen, Code zu kompilieren, der undefiniertes Verhalten liefert?
Pacerier
14
@Pacerier Es gibt bestimmte Dinge, die beim Kompilieren nicht überprüft werden können. Beispielsweise kann nicht immer garantiert werden, dass ein Nullzeiger niemals dereferenziert wird, dies ist jedoch undefiniert.
Tim Seguine
4
@Celeritas, undefiniertes Verhalten kann nicht deterministisch sein. Zum Beispiel ist es unmöglich, im Voraus zu wissen, wie der Inhalt des nicht initialisierten Speichers aussehen wird, z. int f(){int a; return a;}: Der Wert von akann sich zwischen Funktionsaufrufen ändern.
Mark
97

Nun, dies ist im Grunde ein direktes Kopieren und Einfügen aus dem Standard

3.4.1 1 Implementierungsdefiniertes Verhalten Nicht spezifiziertes Verhalten, bei dem jede Implementierung dokumentiert, wie die Auswahl getroffen wird

2 BEISPIEL Ein Beispiel für ein implementierungsdefiniertes Verhalten ist die Ausbreitung des höherwertigen Bits, wenn eine vorzeichenbehaftete Ganzzahl nach rechts verschoben wird.

3.4.3 1 undefiniertes Verhaltensverhalten bei Verwendung eines nicht portierbaren oder fehlerhaften Programmkonstrukts oder fehlerhafter Daten, für die diese Internationale Norm keine Anforderungen stellt

2 HINWEIS Ein mögliches undefiniertes Verhalten reicht vom vollständigen Ignorieren der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer dokumentierten, für die Umgebung charakteristischen Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit die Ausgabe einer Diagnosemeldung).

3 BEISPIEL Ein Beispiel für undefiniertes Verhalten ist das Verhalten beim Ganzzahlüberlauf.

3.4.4 1 nicht spezifiziertes Verhalten Verwendung eines nicht spezifizierten Wertes oder eines anderen Verhaltens, bei dem diese Internationale Norm zwei oder mehr Möglichkeiten bietet und keine weiteren Anforderungen stellt, die in irgendeinem Fall gewählt werden

2 BEISPIEL Ein Beispiel für nicht angegebenes Verhalten ist die Reihenfolge, in der die Argumente für eine Funktion ausgewertet werden.

Ameise
quelle
3
Was ist der Unterschied zwischen implementierungsdefiniertem und nicht spezifiziertem Verhalten?
Zolomon
26
@Zolomon: Genau wie es heißt: Grundsätzlich dasselbe, außer dass bei implementierungsdefinierten Implementierungen dokumentiert werden muss (um zu garantieren), was genau passieren wird, während bei nicht spezifizierten Implementierungen die Implementierung nicht dokumentiert werden muss oder etwas garantieren.
ANT
1
@Zolomon: Das spiegelt sich in der Differenz zwischen 3.4.1 und 2.4.4 wider.
sbi
8
@Celeritas: Hypermoderne Compiler können das besser. Wenn int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }ein Compiler feststellen kann, dass alle Mittel zum Aufrufen der Funktion, mit der die Raketen nicht gestartet werden, undefiniertes Verhalten aufrufen, kann er den Aufruf launch_missiles()bedingungslos ausführen.
Superkatze
2
@northerner Wie im Zitat angegeben, ist nicht angegebenes Verhalten normalerweise auf eine begrenzte Anzahl möglicher Verhaltensweisen beschränkt. In einigen Fällen können Sie sogar zu dem Schluss kommen, dass alle diese Möglichkeiten im gegebenen Kontext akzeptabel sind. In diesen Fällen ist nicht spezifiziertes Verhalten überhaupt kein Problem. Undefiniertes Verhalten ist völlig uneingeschränkt (eb "das Programm kann entscheiden, Ihre Festplatte zu formatieren"). Undefiniertes Verhalten ist immer ein Problem.
Am
60

Vielleicht könnte eine einfache Formulierung für das Verständnis einfacher sein als die strenge Definition der Standards.

Implementierungsdefiniertes Verhalten
Die Sprache sagt, dass wir Datentypen haben. Die Compiler-Anbieter geben an, welche Größen sie verwenden sollen, und stellen eine Dokumentation ihrer Aktivitäten bereit.

undefiniertes Verhalten
Sie machen etwas falsch. Zum Beispiel haben Sie einen sehr großen Wert in einem int, der nicht passtchar . Wie setzen Sie diesen Wert ein char? eigentlich gibt es keinen weg! Alles könnte passieren, aber das Vernünftigste wäre, das erste Byte dieses int zu nehmen und es einzufügen char. Es ist einfach falsch, dies zu tun, um das erste Byte zuzuweisen, aber genau das passiert unter der Haube.

nicht näher bezeichnetes Verhalten
Welche Funktion dieser beiden wird zuerst ausgeführt?

void fun(int n, int m);

int fun1()
{
  cout << "fun1";
  return 1;
}
int fun2()
{
  cout << "fun2";
  return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?

Die Sprache gibt die Bewertung nicht an, von links nach rechts oder von rechts nach links! Ein nicht spezifiziertes Verhalten kann also zu einem undefinierten Verhalten führen oder auch nicht, aber Ihr Programm sollte auf keinen Fall ein nicht spezifiziertes Verhalten erzeugen.


@eSKay Ich denke, deine Frage ist es wert, die Antwort zu bearbeiten, um mehr zu klären :)

zum fun(fun1(), fun2()); ist das Verhalten nicht "Implementierung definiert"? Der Compiler muss doch den einen oder anderen Kurs wählen?

Der Unterschied zwischen implementierungsdefiniert und nicht spezifiziert besteht darin, dass der Compiler im ersten Fall ein Verhalten auswählen soll, im zweiten Fall jedoch nicht. Beispielsweise muss eine Implementierung nur eine Definition von haben sizeof(int). Es kann also nicht gesagt werden, dass dies sizeof(int)4 für einen Teil des Programms und 8 für andere ist. Im Gegensatz zu nicht angegebenem Verhalten, bei dem der Compiler OK sagen kann, werde ich diese Argumente von links nach rechts und die Argumente der nächsten Funktion von rechts nach links auswerten. Es kann im selben Programm passieren, deshalb wird es als nicht spezifiziert bezeichnet . Tatsächlich hätte C ++ einfacher gemacht werden können, wenn einige der nicht spezifizierten Verhaltensweisen angegeben worden wären. Schauen Sie sich hier die Antwort von Dr. Stroustrup an :

Es wird behauptet, dass der Unterschied zwischen dem, was erzeugt werden kann, um dem Compiler diese Freiheit zu geben, und dem Erfordernis einer "gewöhnlichen Bewertung von links nach rechts" erheblich sein kann. Ich bin nicht überzeugt, aber da unzählige Compiler "da draußen" die Freiheit ausnutzen und einige Leute diese Freiheit leidenschaftlich verteidigen, wäre eine Änderung schwierig und könnte Jahrzehnte dauern, bis sie in die entfernten Ecken der C- und C ++ - Welt vordringt. Ich bin enttäuscht, dass nicht alle Compiler vor Code wie ++ i + i ++ warnen. Ebenso ist die Reihenfolge der Bewertung von Argumenten nicht spezifiziert.

IMO bleiben viel zu viele "Dinge" undefiniert, nicht spezifiziert, implementierungsdefiniert usw. Dies ist jedoch leicht zu sagen und sogar Beispiele zu nennen, aber schwer zu beheben. Es sollte auch beachtet werden, dass es nicht allzu schwierig ist, die meisten Probleme zu vermeiden und tragbaren Code zu erzeugen.

AraK
quelle
1
denn fun(fun1(), fun2());ist das nicht das Verhalten "implementation defined"? Der Compiler muss doch den einen oder anderen Kurs wählen?
Lazer
1
@AraK: Danke für die Erklärung. Ich verstehe es jetzt. Übrigens "I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"verstehe ich, dass dies canpassiert. Funktioniert das wirklich mit Compilern, die wir heutzutage verwenden?
Lazer
1
@eSKay Du musst einen Guru danach fragen, der sich mit vielen Compilern die Hände schmutzig gemacht hat :) AFAIK VC bewertet Argumente immer von rechts nach links.
AraK
4
@Lazer: Es kann definitiv passieren. Einfaches Szenario: foo (bar, boz ()) und foo (boz (), bar), wobei bar ein int und boz () eine Funktion ist, die int zurückgibt. Angenommen, eine CPU, bei der erwartet wird, dass Parameter in den Registern R0-R1 übergeben werden. Funktionsergebnisse werden in R0 zurückgegeben; Funktionen können R1 in den Papierkorb werfen. Um "bar" vor "boz ()" auszuwerten, muss eine Kopie der Leiste an einer anderen Stelle gespeichert werden, bevor boz () aufgerufen und diese gespeicherte Kopie geladen wird. Das Auswerten von "bar" nach "boz ()" vermeidet ein Speichern und erneutes Abrufen und ist eine Optimierung, die viele Compiler unabhängig von ihrer Reihenfolge in der Argumentliste durchführen würden.
Supercat
6
Ich weiß nichts über C ++, aber der C-Standard besagt, dass eine Konvertierung eines int in ein Zeichen entweder implementierungsdefiniert oder sogar gut definiert ist (abhängig von den tatsächlichen Werten und der Signatur der Typen). Siehe C99 §6.3.1.3 (unverändert in C11).
Nikolai Ruhe
27

Aus dem offiziellen C-Begründungsdokument

Die Begriffe nicht spezifiziertes Verhalten, undefiniertes Verhalten und implementierungsdefiniertes Verhalten werden verwendet, um das Ergebnis des Schreibens von Programmen zu kategorisieren, deren Eigenschaften der Standard nicht vollständig beschreibt oder nicht vollständig beschreiben kann. Das Ziel dieser Kategorisierung besteht darin, eine bestimmte Vielfalt von Implementierungen zuzulassen, die es ermöglicht, dass die Qualität der Implementierung eine aktive Kraft auf dem Markt ist, sowie bestimmte beliebte Erweiterungen zuzulassen, ohne das Gütesiegel der Konformität mit dem Standard zu entfernen. Anhang F des Standards katalogisiert die Verhaltensweisen, die in eine dieser drei Kategorien fallen.

Nicht spezifiziertes Verhalten gibt dem Implementierer einen gewissen Spielraum bei der Übersetzung von Programmen. Dieser Spielraum reicht nicht so weit, dass das Programm nicht übersetzt werden kann.

Undefiniertes Verhalten gibt dem Implementierer die Lizenz, bestimmte Programmfehler, die schwer zu diagnostizieren sind, nicht abzufangen. Es werden auch Bereiche mit möglichen konformen Spracherweiterungen identifiziert: Der Implementierer kann die Sprache erweitern, indem er eine Definition des offiziell nicht definierten Verhaltens bereitstellt.

Durch die Implementierung definiertes Verhalten gibt einem Implementierer die Freiheit, den geeigneten Ansatz zu wählen, erfordert jedoch, dass diese Auswahl dem Benutzer erklärt wird. Als implementierungsdefiniert bezeichnete Verhaltensweisen sind im Allgemeinen solche, bei denen ein Benutzer basierend auf der Implementierungsdefinition aussagekräftige Codierungsentscheidungen treffen kann. Implementierer sollten dieses Kriterium berücksichtigen, wenn sie entscheiden, wie umfangreich eine Implementierungsdefinition sein soll. Wie bei nicht angegebenem Verhalten ist es keine angemessene Antwort, die Quelle, die das implementierungsdefinierte Verhalten enthält, einfach nicht zu übersetzen.

Johannes Schaub - litb
quelle
3
Hypermoderne Compiler-Writer betrachten "undefiniertes Verhalten" auch als Erlaubnis für Compiler-Writer, anzunehmen, dass Programme niemals Eingaben empfangen, die undefiniertes Verhalten verursachen würden, und willkürlich alle Aspekte des Verhaltens der Programme zu ändern, wenn sie solche Eingaben erhalten.
Superkatze
2
Ein weiterer Punkt, den ich gerade bemerkt habe: C89 verwendete den Begriff "Erweiterung" nicht, um Funktionen zu beschreiben, die bei einigen Implementierungen garantiert waren, bei anderen jedoch nicht. Die Autoren von C89 erkannten, dass die Mehrheit der damals aktuellen Implementierungen vorzeichenbehaftete Arithmetik und vorzeichenlose Arithmetik identisch behandeln würde, es sei denn, die Ergebnisse wurden auf bestimmte Weise verwendet, und diese Behandlung wurde auch im Falle eines vorzeichenbehafteten Überlaufs angewendet. Sie haben dies jedoch nicht als gemeinsame Erweiterung in Anhang J2 aufgeführt, was mir nahe legt, dass sie es eher als einen natürlichen Zustand als als eine Erweiterung betrachteten.
Supercat
10

Undefiniertes Verhalten vs. nicht spezifiziertes Verhalten enthält eine kurze Beschreibung.

Ihre letzte Zusammenfassung:

Zusammenfassend lässt sich sagen, dass Sie sich über nicht angegebenes Verhalten normalerweise keine Sorgen machen sollten, es sei denn, Ihre Software muss portabel sein. Umgekehrt ist undefiniertes Verhalten immer unerwünscht und sollte niemals auftreten.

Anders Abel
quelle
1
Es gibt zwei Arten von Compilern: solche, die, sofern nicht ausdrücklich anders dokumentiert, die meisten Formen des undefinierten Verhaltens des Standards so interpretieren, dass sie auf charakteristische Verhaltensweisen zurückgreifen, die von der zugrunde liegenden Umgebung dokumentiert werden, und solche, die standardmäßig nur Verhaltensweisen offenlegen, als die der Standard charakterisiert Implementierungsdefiniert. Bei Verwendung von Compilern des ersten Typs können viele Dinge des ersten Typs mit UB effizient und sicher ausgeführt werden. Compiler für den zweiten Typ sind nur dann für solche Aufgaben geeignet, wenn sie Optionen bieten, um das Verhalten in solchen Fällen zu gewährleisten.
Supercat
8

In der Vergangenheit stellten sowohl implementierungsdefiniertes Verhalten als auch undefiniertes Verhalten Situationen dar, in denen die Autoren des Standards erwarteten, dass Personen, die Qualitätsimplementierungen schreiben, mit Urteilsvermögen entscheiden würden, welche Verhaltensgarantien, falls vorhanden, für Programme in dem beabsichtigten Anwendungsfeld nützlich sind, das auf dem Programm ausgeführt wird beabsichtigte Ziele. Die Anforderungen von High-End-Code für das Knacken von Zahlen unterscheiden sich erheblich von denen von Low-Level-Systemcode, und sowohl UB als auch IDB bieten Compiler-Autoren Flexibilität, um diese unterschiedlichen Anforderungen zu erfüllen. Keine der beiden Kategorien schreibt vor, dass sich Implementierungen so verhalten, dass sie für einen bestimmten Zweck oder sogar für einen beliebigen Zweck nützlich sind. Qualitätsimplementierungen, die behaupten, für einen bestimmten Zweck geeignet zu sein, sollten sich jedoch in einer Weise verhalten, die diesem Zweck angemessen istob der Standard dies erfordert oder nicht .

Der einzige Unterschied zwischen implementierungsdefiniertem Verhalten und nicht definiertem Verhalten besteht darin, dass erstere erfordern, dass Implementierungen ein konsistentes Verhalten definieren und dokumentieren, selbst wenn nichts, was die Implementierung möglicherweise tun könnte, nützlich wäre . Die Trennlinie zwischen ihnen besteht nicht darin, ob es für Implementierungen im Allgemeinen nützlich wäre, Verhaltensweisen zu definieren (Compiler-Autoren sollten nützliche Verhaltensweisen definieren, wenn dies nach dem Standard erforderlich ist oder nicht), sondern ob es Implementierungen geben könnte, bei denen das Definieren eines Verhaltens gleichzeitig kostspielig wäre und nutzlos . Ein Urteil darüber, dass solche Implementierungen existieren könnten, impliziert in keiner Weise, Form oder Form ein Urteil über die Nützlichkeit der Unterstützung eines definierten Verhaltens auf anderen Plattformen.

Leider haben Compiler-Autoren seit Mitte der neunziger Jahre damit begonnen, das Fehlen von Verhaltensmandaten als ein Urteil zu interpretieren, dass Verhaltensgarantien die Kosten selbst in Anwendungsbereichen, in denen sie von entscheidender Bedeutung sind, und sogar auf Systemen, in denen sie praktisch nichts kosten, nicht wert sind. Anstatt UB als Aufforderung zu angemessenem Urteilsvermögen zu behandeln, haben Compiler-Autoren damit begonnen, dies als Entschuldigung dafür zu betrachten, dies nicht zu tun.

Beispiel: Geben Sie den folgenden Code ein:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

Eine Zwei-Komplement-Implementierung müsste keinerlei Aufwand erfordern, um den Ausdruck v << powals Zwei-Komplement-Verschiebung zu behandeln , ohne Rücksicht darauf, ob er vpositiv oder negativ ist.

Die bevorzugte Philosophie einiger heutiger Compiler-Autoren würde jedoch darauf vhinweisen, dass es keinen Grund gibt, das Programm den negativen Bereich von abschneiden zu lassen , da es nur negativ sein kann, wenn sich das Programm auf undefiniertes Verhalten einlässt v. Obwohl die Linksverschiebung negativer Werte früher von jedem einzelnen Compiler von Bedeutung unterstützt wurde und eine große Menge vorhandenen Codes auf diesem Verhalten beruht, würde die moderne Philosophie die Tatsache interpretieren, dass der Standard sagt, dass linksverschiebende negative Werte UB sind als Dies bedeutet, dass Compiler-Autoren dies ignorieren sollten.

Superkatze
quelle
Aber ein undefinierter Umgang mit undefiniertem Verhalten ist nicht kostenlos. Der ganze Grund, warum moderne Compiler in einigen Fällen von UB ein derart bizarres Verhalten zeigen, ist, dass sie unermüdlich optimieren. Um die beste Arbeit zu leisten, müssen sie davon ausgehen können, dass UB niemals auftritt.
Tom Swirly
Aber die Tatsache, dass <<UB auf negativen Zahlen steht, ist eine böse kleine Falle, und ich bin froh, daran erinnert zu werden!
Tom Swirly
1
@TomSwirly: Leider ist es Compiler-Autoren egal, dass das Anbieten von losen Verhaltensgarantien, die über die vom Standard vorgeschriebenen hinausgehen, häufig einen massiven Geschwindigkeitsschub ermöglichen kann, verglichen mit der Forderung, dass Code um jeden Preis alles vermeidet, was nicht durch den Standard definiert ist. Wenn es einem Programmierer egal ist, ob er i+j>k1 oder 0 ergibt, wenn die Addition überläuft, sofern er keine anderen Nebenwirkungen hat , kann ein Compiler möglicherweise einige massive Optimierungen vornehmen, die nicht möglich wären, wenn der Programmierer den Code als geschrieben hätte (int)((unsigned)i+j) > k.
Superkatze
1
@TomSwirly: Wenn Compiler X ein streng konformes Programm verwenden kann, um eine Aufgabe T auszuführen und eine ausführbare Datei zu erhalten, die 5% effizienter ist als Compiler Y mit demselben Programm, bedeutet dies, dass X besser ist, selbst wenn Y. könnte Code generieren, der dieselbe Aufgabe dreimal so effizient erledigt, wenn ein Programm Verhaltensweisen ausnutzt, die Y garantiert, X jedoch nicht.
Supercat
6

C ++ Standard n3337 § 1.3.10 Implementierungsdefiniertes Verhalten

Verhalten für ein wohlgeformtes Programmkonstrukt und korrekte Daten, das von der Implementierung abhängt und das jede Implementierung dokumentiert

Manchmal schreibt C ++ Standard einigen Konstrukten kein bestimmtes Verhalten vor, sondern sagt stattdessen, dass ein bestimmtes, genau definiertes Verhalten von einer bestimmten Implementierung (Version der Bibliothek) ausgewählt und beschrieben werden muss. So kann der Benutzer immer noch genau wissen, wie sich das Programm verhält, obwohl Standard dies nicht beschreibt.


C ++ Standard n3337 § 1.3.24 undefiniertes Verhalten

Verhalten, für das diese Internationale Norm keine Anforderungen stellt [Hinweis: Undefiniertes Verhalten kann erwartet werden, wenn diese Internationale Norm keine explizite Definition des Verhaltens enthält oder wenn ein Programm ein fehlerhaftes Konstrukt oder fehlerhafte Daten verwendet. Das zulässige undefinierte Verhalten reicht vom vollständigen Ignorieren der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer für die Umgebung charakteristischen dokumentierten Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit der Ausgabe) einer Diagnosemeldung). Viele fehlerhafte Programmkonstrukte erzeugen kein undefiniertes Verhalten. Sie müssen diagnostiziert werden. - Endnote]

Wenn das Programm auf ein Konstrukt stößt, das nicht gemäß C ++ Standard definiert ist, kann es tun, was es will (vielleicht eine E-Mail an mich senden oder eine E-Mail an Sie senden oder den Code vollständig ignorieren).


C ++ Standard n3337 § 1.3.25 nicht spezifiziertes Verhalten

Verhalten für ein wohlgeformtes Programmkonstrukt und korrekte Daten, das von der Implementierung abhängt [Hinweis: Die Implementierung ist nicht erforderlich, um zu dokumentieren, welches Verhalten auftritt. Der Bereich möglicher Verhaltensweisen wird normalerweise durch diese Internationale Norm beschrieben. - Endnote]

C ++ Standard legt einigen Konstrukten kein bestimmtes Verhalten auf, sondern sagt stattdessen, dass ein bestimmtes, genau definiertes Verhalten durch eine bestimmte Implementierung (Version der Bibliothek) ausgewählt werden muss ( Bot nicht unbedingt beschrieben ). In dem Fall, in dem keine Beschreibung angegeben wurde, kann es für den Benutzer schwierig sein, genau zu wissen, wie sich das Programm verhält.

4pie0
quelle
6

Implementierung definiert-

Implementierer wünschen, sollten gut dokumentiert sein, Standard gibt Auswahlmöglichkeiten, aber sicher zu kompilieren

Nicht spezifiziert -

Entspricht der Implementierung, ist jedoch nicht dokumentiert

Nicht definiert-

Alles könnte passieren, kümmere dich darum.

Suraj K Thomas
quelle
2
Ich denke, es ist wichtig anzumerken, dass sich die praktische Bedeutung von "undefiniert" in den letzten Jahren geändert hat. Früher war es so, dass die uint32_t s;Bewertung, 1u<<swann s33 ist, vielleicht 0 oder 2 ergeben würde, aber nichts anderes Verrücktes tun würde. Neuere Compiler, die auswerten, 1u<<skönnen jedoch dazu führen, dass ein Compiler feststellt, dass sCode vor oder nach diesem Ausdruck, der nur dann relevant wäre, wenn er zuvor weniger als 32 gewesen sein musss er 32 oder mehr gewesen wäre, weggelassen werden .
Supercat