c++ - Undefiniertes, nicht spezifiziertes und implementierungsdefiniertes Verhalten




undefined-behavior c++-faq (6)

Aus dem offiziellen C Rationale Dokument

Die Begriffe nicht spezifiziertes Verhalten, nicht definiertes Verhalten und implementierungsdefiniertes Verhalten werden verwendet, um das Ergebnis von Schreibprogrammen zu kategorisieren, deren Eigenschaften der Standard nicht oder nicht vollständig beschreiben kann. Das Ziel dieser Kategorisierung ist es, eine gewisse Vielfalt bei den Implementierungen zu ermöglichen, was es ermöglicht, dass die Qualität der Implementierung eine aktive Kraft auf dem Markt ist und bestimmte populäre Erweiterungen erlaubt, ohne den Standard der Konformität mit dem Standard zu beseitigen. Anhang F zum Standard führt diese Verhaltensweisen auf, die in eine dieser drei Kategorien fallen.

Unspezifiziertes Verhalten gibt dem Implementierer einen gewissen Spielraum beim Übersetzen von Programmen. Dieser Spielraum reicht nicht so weit, dass das Programm nicht übersetzt werden kann.

Undefiniertes Verhalten gibt der Implementor-Lizenz, bestimmte Programmfehler nicht zu erkennen, die schwer zu diagnostizieren sind. Es identifiziert auch Bereiche einer möglichen übereinstimmenden Spracherweiterung: Der Implementierer kann die Sprache erweitern, indem er eine Definition des offiziell undefinierten Verhaltens bereitstellt.

Implementationsdefiniertes Verhalten gibt einem Implementierer die Freiheit, den geeigneten Ansatz zu wählen, erfordert jedoch, dass diese Wahl dem Benutzer erklärt wird. Verhalten, die als implementierungsdefiniert bezeichnet werden, sind im Allgemeinen solche, bei denen ein Benutzer basierend auf der Implementierungsdefinition sinnvolle Codierungsentscheidungen treffen kann. Die Bearbeiter sollten dieses Kriterium berücksichtigen, wenn sie entscheiden, wie umfangreich eine Umsetzungsdefinition sein sollte. Wie bei einem nicht spezifizierten Verhalten ist es nicht angemessen, einfach die Quelle zu übersetzen, die das implementierungsdefinierte Verhalten enthält.

Was ist der Unterschied zwischen undefiniertem, nicht spezifiziertem und implementierungsdefiniertem Verhalten in C und C ++?


C ++ Standard n3337 § 1.3.10 implementierungsdefiniertes Verhalten

Verhalten, für ein wohlgeformtes Programm konstruieren und korrigieren Daten, die von der Implementierung abhängt und dass jede Implementierung dokumentiert

Manchmal erlegt C ++ Standard bestimmten Konstrukten kein bestimmtes Verhalten auf, 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 verhalten wird, obwohl Standard dies nicht beschreibt.

C ++ Standard n3337 § 1.3.24 undefiniertes Verhalten

Verhalten, für das dieser Internationale Standard keine Anforderungen enthält [Hinweis: Undefiniertes Verhalten ist zu erwarten, wenn dieser Internationale Standard eine explizite Definition von Verhalten auslässt oder wenn ein Programm ein fehlerhaftes Konstrukt oder fehlerhafte Daten verwendet. Zulässiges undefiniertes Verhalten reicht von der vollständigen Ignorierung der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer dokumentierten Weise, die für die Umgebung charakteristisch ist (mit oder ohne die Ausgabe einer Diagnosemeldung), bis zur Beendigung einer Übersetzung oder Ausführung einer Diagnosemeldung). Viele fehlerhafte Programmkonstrukte erzeugen kein undefiniertes Verhalten; Sie müssen diagnostiziert werden. - Endnote]

Wenn das Programm auf ein Konstrukt trifft, das nicht nach C ++ Standard definiert ist, kann es tun, was es tun möchte (vielleicht eine E-Mail an mich senden oder vielleicht eine E-Mail an Sie senden oder den Code komplett ignorieren).

C ++ Standard n3337 § 1.3.25 nicht spezifiziertes Verhalten

Verhalten für ein wohlgeformtes Programmkonstrukt und korrekte Daten, die von der Implementierung abhängen [Hinweis: Die Implementierung ist nicht erforderlich, um zu dokumentieren, welches Verhalten auftritt. Die Bandbreite möglicher Verhaltensweisen wird in der Regel durch diese Internationale Norm abgegrenzt. - Endnote]

C ++ Standard legt bestimmten Konstrukten kein bestimmtes Verhalten auf, sagt aber stattdessen, dass ein bestimmtes, wohldefiniertes Verhalten von einer bestimmten Implementierung (Version der Bibliothek) gewählt werden muss ( nicht unbedingt beschrieben ). In dem Fall, wenn keine Beschreibung bereitgestellt wurde, kann es für den Benutzer schwierig sein, genau zu wissen, wie sich das Programm verhalten wird.


In der Vergangenheit stellten sowohl das implementierungsdefinierte Verhalten als auch das nicht definierte Verhalten Situationen dar, in denen die Autoren des Standards erwarteten, dass Personen, die qualitativ hochwertige Implementierungen schreiben, Entscheidungen treffen würden, um zu entscheiden, welche Verhaltensgarantien für Programme in dem vorgesehenen Anwendungsfeld auf dem Server nützlich sein könnten beabsichtigte Ziele. Die Anforderungen an Code-High-End-Code unterscheiden sich erheblich von denen für Low-Level-Systeme, und sowohl UB als auch IDB geben Compiler-Autoren Flexibilität, um diese unterschiedlichen Anforderungen zu erfüllen. Keine dieser 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 dem Zweck entspricht, unabhängig davon, ob der Standard dies erfordert oder nicht .

Der einzige Unterschied zwischen implementierungsdefiniertem Verhalten und nicht definiertem Verhalten besteht darin, dass erstere festlegen müssen, dass Implementierungen ein konsistentes Verhalten selbst dann definieren und dokumentieren, wenn nichts, was die Implementierung möglicherweise tun könnte, nützlich wäre . Die Trennlinie zwischen ihnen ist nicht, ob es für Implementierungen im Allgemeinen nützlich wäre, Verhalten zu definieren (Compiler-Schreiber sollten sinnvolles Verhalten definieren, wenn der Standard dies erfordert oder nicht), aber es könnte Implementierungen geben, bei denen die Definition eines Verhaltens gleichzeitig kostspielig wäre und nutzlos . Eine Beurteilung, dass solche Implementierungen existieren könnten, impliziert in keiner Weise, Form oder Form, irgendein Urteil über die Nützlichkeit, ein definiertes Verhalten auf anderen Plattformen zu unterstützen.

Unglücklicherweise interpretieren Compiler-Autoren seit Mitte der 1990er Jahre das Fehlen von Verhaltensmandaten als ein Urteil, dass Verhaltensgarantien selbst in Anwendungsbereichen, in denen sie lebenswichtig sind, und sogar auf Systemen, bei denen sie praktisch nichts kosten, die Kosten nicht wert sind. Anstatt UB als eine Einladung zu einem vernünftigen Urteil zu betrachten, haben Compiler-Autoren damit begonnen, es als Entschuldigung zu betrachten, dies nicht zu tun.

Zum Beispiel mit dem folgenden Code gegeben:

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

eine Zweierkomplement-Implementierung müsste keinerlei Anstrengung aufwenden, um den Ausdruck v << pow als Zweierkomplement-Verschiebung zu behandeln, ohne zu berücksichtigen, ob v positiv oder negativ ist.

Die bevorzugte Philosophie unter einigen der heutigen Compiler-Autoren würde jedoch nahelegen, dass, weil v nur negativ sein kann, wenn das Programm Undefined Behaviour ausführen wird, es keinen Grund gibt, dass das Programm den negativen Bereich von v . Obwohl die Verschiebung negativer Werte nach links bei jedem einzelnen Bedeutungscompiler unterstützt wurde und eine große Menge bestehenden Codes auf diesem Verhalten beruht, würde die moderne Philosophie die Tatsache interpretieren, dass der Standard sagt, dass die linksverschiebenden negativen Werte UB als sind was bedeutet, dass Compiler-Autoren sich frei fühlen sollten, dies zu ignorieren.


Nun, das ist im Grunde eine reine Kopierpaste aus dem Standard

3.4.1 1 implementierungsdefiniertes verhaltensspezifisches Verhalten, bei dem jede Implementierung dokumentiert, wie die Entscheidung getroffen wird

2 BEISPIEL Ein Beispiel für implementierungsdefiniertes Verhalten ist die Propagierung des höherwertigen Bits, wenn eine ganze Zahl mit Vorzeichen nach rechts verschoben wird.

3.4.3 1 undefiniertes Verhalten bei Verwendung eines nicht portablen oder fehlerhaften Programmkonstrukts oder fehlerhafter Daten, für die in dieser Internationalen Norm keine Anforderungen gestellt werden

2 HINWEIS Mögliches undefiniertes Verhalten reicht von der vollständigen Ignorierung der Situation mit unvorhersehbaren Ergebnissen über das Verhalten bei der Übersetzung oder Programmausführung in einer dokumentierten Weise, die für die Umgebung charakteristisch ist (mit oder ohne Ausgabe einer Diagnosemeldung), bis zum Abbruch einer Übersetzung oder Ausführung die Ausgabe einer Diagnosemeldung).

3 BEISPIEL Ein Beispiel für undefiniertes Verhalten ist das Verhalten bei Integerüberlauf.

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

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


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

Ihre endgültige Zusammenfassung:

Zusammenfassend lässt sich sagen, dass ein nicht spezifiziertes Verhalten normalerweise nicht zu befürchten ist, es sei denn, Ihre Software muss portabel sein. Umgekehrt ist undefiniertes Verhalten immer unerwünscht und sollte niemals auftreten.


Undefiniertes Verhalten ist einer dieser Aspekte der C- und C ++ - Sprache, der für Programmierer aus anderen Sprachen überraschend sein kann (andere Sprachen versuchen, sie 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 p zeigt 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 ruft es undefiniertes Verhalten auf :

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

Ich kann Leute schreien hören "Aber warte, ich kann das kompilieren kein Problem und bekomme die Ausgabe yellow " oder "Was meinst du undefined, String-Literale werden im Nur-Lese-Speicher gespeichert, so dass der erste Zuweisungsversuch zu einem Core-Dump führt" . Dies ist genau das Problem mit undefiniertem Verhalten. Im Grunde erlaubt der Standard alles, was passiert, wenn Sie undefiniertes Verhalten (sogar nasale Dämonen) aufrufen. Wenn es ein "korrektes" Verhalten gemäß Ihrem mentalen Modell der Sprache gibt, ist dieses Modell einfach falsch; Der C ++ - Standard hat die einzige Abstimmungsperiode.

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

Abschnitt 1.9 des C ++ - Standards erwähnt auch zwei weniger gefährliche Brüder des unbestimmten Verhaltens, nicht spezifiziertes Verhalten und implementationsdefiniertes Verhalten :

Die semantischen Beschreibungen in diesem Internationalen Standard definieren eine parametrisierte nichtdeterministische abstrakte Maschine.

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

Bestimmte andere Aspekte und Operationen der abstrakten Maschine werden in dieser Internationalen Norm als nicht spezifiziert beschrieben (z. B. Reihenfolge der Auswertung von Argumenten für eine Funktion). Soweit möglich, 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 nicht definiert beschrieben (z. B. die Auswirkung der Dereferenzierung des Nullzeigers). [ Anmerkung : Diese Internationale Norm enthält keine Anforderungen an das Verhalten von Programmen, die undefiniertes Verhalten enthalten. - Endnote ]

Im Einzelnen heißt es in Abschnitt 1.3.24:

Zulässiges undefiniertes Verhalten reicht von der vollständigen Ignorierung der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer dokumentierten Weise, die für die Umgebung charakteristisch ist (mit oder ohne die Ausgabe einer Diagnosemeldung), bis zur Beendigung einer Übersetzung oder Ausführung einer Diagnosemeldung).

Was können Sie tun, um nicht in undefiniertes Verhalten zu geraten? Im Grunde müssen Sie gute C ++ Bücher von Autoren lesen, die wissen, worüber sie sprechen. Schrauben Sie Internet-Tutorials. Schraube bullschildt.







unspecified-behavior