Warum gibt es „undefiniertes Verhalten“? [duplicate]

Lesezeit: 4 Minuten

Bestimmte gängige Programmiersprachen, insbesondere C und C++, haben den starken Begriff von undefiniertes Verhalten: Wenn Sie versuchen, bestimmte Operationen außerhalb der beabsichtigten Weise auszuführen, führt dies zu undefiniertem Verhalten.

Wenn undefiniertes Verhalten auftritt, darf ein Compiler alles tun (einschließlich gar nichts, „Zeitreisen“ usw.), was er will.

Meine Frage ist: Warum existiert dieser Begriff des undefinierten Verhaltens? Soweit ich sehen kann, würde eine riesige Menge an Fehlern, Programmen, die auf einer Version eines Compilers funktionieren, auf der nächsten nicht mehr funktionieren usw. verhindert werden, wenn die Verwendung der Operationen nicht zu undefiniertem Verhalten führen würde, sondern zu undefiniertem Verhalten führen würde A Kompilierungsfehler.

Warum ist das nicht so?

  • So ziemlich diese Referenz ist die Anlaufstelle für UB: Was jeder C-Programmierer über UB wissen sollte

    – Mike Rebe

    27. Juli 2018 um 12:25 Uhr

  • Wegen der Ideologie des C. Sehr flexibel und leistungsfähig, alles in den Händen des Programmierers lassend

    – 0___________

    27. Juli 2018 um 12:25 Uhr

  • Interessanter Vortrag zum Thema von Chandler Carruth: Garbage In, Garbage Out: Streit um undefiniertes Verhalten…

    – Borgführer

    27. Juli 2018 um 12:31 Uhr

  • „Die Verwendung des Betriebs außerhalb des bestimmungsgemäßen Gebrauchs würde dazu führen, dass a Kompilierungsfehler„Die meisten undefinierten Verhaltensweisen in C sind nicht statisch erkennbar, also können sie keine Kompilierungsfehler sein. Sie müssten Laufzeitfehler sein, die mit Laufzeitkosten einhergehen würden.

    – sepp2k

    27. Juli 2018 um 12:32 Uhr

  • Obwohl es ein interessantes und wichtiges Thema ist, ist es zu weit gefasst. Es gab unzählige Diskussionen und Forschungen zu genau dieser Frage, und es gibt immer noch Sprachen, die von überhaupt keinem UB bis hin zu überall vorhandenem UB reichen (ähm, C/C++).

    – Passant

    27. Juli 2018 um 13:19 Uhr


Benutzeravatar von eerorika
Erorika

Warum existiert diese Vorstellung von undefiniertem Verhalten?

Damit die Sprache/Bibliothek auf einer Vielzahl unterschiedlicher Computerarchitekturen so effizient wie möglich implementiert werden kann (- und vielleicht im Fall von C – während die Implementierung einfach bleibt).

wenn statt undefiniertem Verhalten die Verwendung der Operationen außerhalb ihrer beabsichtigten Verwendung einen Kompilierungsfehler verursachen würde

In den meisten Fällen von undefiniertem Verhalten ist es unmöglich – oder unerschwinglich ressourcenintensiv – zu beweisen, dass undefiniertes Verhalten zur Kompilierzeit für alle Programme im Allgemeinen existiert.

Manche Fälle sind nachweisbar manche Programme, aber es ist nicht möglich anzugeben, welche dieser Fälle erschöpfend sind, und daher wird der Standard dies nicht versuchen. Trotzdem sind einige Compiler schlau genug, einige einfache Fälle von UB zu erkennen, und diese Compiler werden den Programmierer davor warnen. Beispiel:

int arr[10];
return arr[10];

Dieses Programm hat undefiniertes Verhalten. Eine bestimmte Version von GCC, die ich getestet habe, zeigt:

Warnung: Array-Index 10 liegt über den Array-Grenzen von ‘int [10]’ [-Warray-bounds]

Es ist kaum eine gute Idee, eine Warnung wie diese zu ignorieren.


Eine typischere Alternative zu undefiniertem Verhalten wäre eine definierte Fehlerbehandlung in solchen Fällen, z. B. das Auslösen einer Ausnahme (vergleichen Sie beispielsweise Java, wo der Zugriff auf eine Nullreferenz eine Ausnahme vom Typ verursacht java.lang.NullPointerException geworfen werden). Die Überprüfung der Vorbedingungen für wohldefiniertes Verhalten ist jedoch langsamer, als es nicht zu überprüfen.

Indem die Vorbedingungen nicht überprüft werden, gibt die Sprache dem Programmierer die Möglichkeit, die Korrektheit selbst zu beweisen, und vermeidet dadurch den Laufzeit-Overhead der Überprüfung in einem Programm, das sie nachweislich nicht benötigt. Tatsächlich geht mit dieser Macht eine große Verantwortung einher.

Heutzutage kann die Beweislast für die Wohldefiniertheit des Programms durch die Verwendung von Tools (Beispiel), die einige dieser Laufzeitprüfungen hinzufügen und das Programm bei fehlgeschlagener Prüfung ordentlich beenden.

  • Gut gesagt! Plattformeffizienz und Codeportabilität haben C und C++ stark beeinflusst und geformt. Einige der C++11-Verbesserungen, wie z. B. die Bewegungssemantik, sollten einen Mangel an potenzieller Effizienz beheben. Aber sowohl Effizienz als auch Portabilität sind schwer zu erreichen, ohne den Compilern viel Spielraum zu geben … undefiniertes Verhalten. Andere Sprachen mit weniger undefiniertem Verhalten können weniger leistungsfähig sein (manchmal viel weniger leistungsfähig). Es ist ein Kompromiss, und verschiedene Sprachen haben unterschiedliche Ziele. Sprachen sind Werkzeuge, die in ihrem Bereich geeignet sind.

    – Eljay

    27. Juli 2018 um 12:44 Uhr

  • Ich würde +2 geben, wenn ich könnte. Ich würde hinzufügen, dass neuere (Versionen von) Sprachen versuchen, den Umfang von UB zu minimieren, indem sie der Sprache explizitere Regeln hinzufügen (nehmen Sie zum Beispiel Rust und verschieben Sie die Semantik in C ++ 11).

    – Bartop

    27. Juli 2018 um 13:05 Uhr

  • Eine weitere gängige Alternative für undefiniertes Verhalten besteht darin, ein Ergebnis anzugeben, das dem natürlichen Verhalten vieler Zielplattformen entspricht. Beispielsweise gibt Java an, dass 65535*65537 so umlaufen, dass -1 ergibt, und ein Verschiebungsausdruck wie 1 << 35 reduziert den Verschiebungsbetrag mod 32 (ergibt 3), bevor die Verschiebung durchgeführt wird, was somit ergibt 8.

    – Superkatze

    27. Juli 2018 um 20:30 Uhr


  • Ein klassisches Beispiel für Plattform-Inkonsistenzen ist Out-of-Bounds Shift: auto undef_shift(std::uint32_t v) { return v << 32; }. Bei einigen Architekturen fängt der entsprechende Linksverschiebungsbefehl ab, bei anderen gibt er immer Null zurück (da er als Herausschieben aller Bits behandelt wird) und bei wieder anderen würde er sich genauso verhalten wie return v; weil die höheren Bits des zweiten Operanden stillschweigend ignoriert werden (dies gilt für x86). Hätten sie ein bestimmtes Verhalten vorgeschrieben, würden alle anderen Plattformen durch zusätzlichen Bereinigungs-/Prüfcode, den der Compiler ausgeben müsste, stark bestraft.

    – Arne Vogel

    28. Juli 2018 um 11:10 Uhr

  • @ArneVogel: Java spezifiziert Mod-32-Reduktion, unabhängig von der Architektur. Java-Implementierungen auf einem ARM müssen eine UND-Operation hinzufügen, wenn sie nicht überprüfen können, ob der Operand zwischen 0 und 31 liegt. Auf der anderen Seite, wenn eine Sprache spezifiziert ist, wäre das Ergebnis eine nicht spezifizierte Wahl zwischen x<<(y-1)<<1 Und x<<(y & 31) hätte auf vielen Plattformen einen effizienten Betrieb ermöglicht und es dennoch ermöglicht (x<<y)|(x>>(32-y)) um eine effiziente Möglichkeit zu sein, eine Drehung durchzuführen.

    – Superkatze

    28. Juli 2018 um 16:37 Uhr


Benutzeravatar von Michael Kenzel
Michael Kenzel

Undefiniertes Verhalten existiert hauptsächlich, um dem Compiler Freiheit zur Optimierung zu geben. Eine Sache, die es dem Compiler zum Beispiel erlaubt, ist, unter der Annahme zu arbeiten, dass bestimmte Dinge nicht passieren können (ohne zuerst beweisen zu müssen, dass sie nicht passieren können, was oft sehr schwierig oder unmöglich wäre). Indem er davon ausgeht, dass bestimmte Dinge nicht passieren können, kann der Compiler dies tun eliminieren/muss nicht generiert werden Code, der ansonsten benötigt würde, um bestimmte Möglichkeiten zu berücksichtigen.

Guter Vortrag zum Thema

  • Gibt es irgendetwas in der C89-Begründung oder einer anderen Dokumentation aus den 1980er Jahren, das diese Ansicht stützt, oder ist es eine modernere Erfindung?

    – Superkatze

    27. Juli 2018 um 17:11 Uhr

  • Dies ist eine gute Antwort. Undefiniertes Verhalten ermöglicht es dem Compiler, einfach anzunehmen, dass bestimmte Dinge nie passieren, und kann entsprechend optimieren. Das Wegoptimieren einer Prüfung oder Verzweigung, die nur bei undefiniertem Verhalten erfolgen würde, wäre nicht möglich, wenn der Compiler gezwungen wäre, stattdessen zu prüfen und einen Fehler zu verursachen. Stattdessen sind die Ergebnisse „unvorhersehbar“, genauso wie die Ergebnisse der Annahme, dass etwas, das nie passiert, dann doch passiert, unvorhersehbar sind.

    – Benutzer16217248

    16. Januar um 16:03 Uhr

Undefiniertes Verhalten basiert hauptsächlich auf dem Ziel, auf dem es ausgeführt werden soll. Der Compiler ist nicht verantwortlich für das dynamische Verhalten des Programms oder das statische Verhalten in dieser Angelegenheit. Die Compiler-Prüfungen sind auf die Regeln der Sprache beschränkt und einige moderne Compiler führen auch ein gewisses Maß an statischer Analyse durch.

Ein typisches Beispiel wären nicht initialisierte Variablen. Es existiert aufgrund der Syntaxregeln von C, wo eine Variable ohne Init-Wert deklariert werden kann. Einige Compiler weisen solchen Variablen 0 zu und andere weisen der Variablen einfach einen Mem-Zeiger zu und verlassen sie einfach so. Wenn das Programm diese Variablen nicht initialisiert, führt dies zu undefiniertem Verhalten.

  • Ich denke, Sie antworten “warum implementieren Compiler undefiniertes Verhalten”, aber die Frage ist “warum implementiert der Sprachstandard undefiniertes Verhalten”.

    – VLL

    18. November 2022 um 12:01 Uhr

  • “Es existiert aufgrund der Syntaxregeln von C”. Das ist falsch: „Undefiniertes Verhalten“ bedeutet nicht Dinge, die in der Norm nicht erwähnt werden. Der Standard gibt klar an, welche Verhaltensweisen zu „undefiniertem Verhalten“ führen. Die Designer der Sprache haben bereits jede Situation berücksichtigt und beschlossen, sie zu einem “undefinierten Verhalten” zu machen. Sie hätten genauso einfach ein bestimmtes Verhalten für diese Situationen definieren und es den Compiler-Programmierern überlassen können, herauszufinden, wie man das implementiert.

    – VLL

    18. November 2022 um 12:01 Uhr

1443680cookie-checkWarum gibt es „undefiniertes Verhalten“? [duplicate]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy