Undefiniertes Verhalten in C++ (Undefiniertes Verhalten in C++), Lektion, Seite 724094
https://www.purl.org/stefan_ram/pub/undefiniertes_c++ (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram
C++-Kurs

Undefiniertes Verhalten in C++ 

Wenn ein Programm undefiniertes Verhalten  (“undefined behavior ”, abgekürzt “UB ”) zeigt, so ist die C++ -Implementation von allen ihren Verpflichtungen bei der Ausführung (einschließlich der Übersetzung) jenes Programms entbunden. Anders gesagt, kann die Implementation und das Programm dann also „machen was es will“.

Eine C++ -Implementation ist nicht immer in der Lage und daher nicht dazu verpflichtet, undefiniertes Verhalten zu erkennen und zu melden. Vielmehr ist es die Aufgabe des C++ -Programmierers, Ausführungen mit undefiniertem Verhalten zu vermeiden.

Es ist ein Fehler des Programmierers, ein Programm zu schreiben, das unter den Umständen, unter denen es eingesetzt wird, undefiniertes Verhalten zeigt.

Ein Programm, das eine Division durch »0« oder »0.0« enthält, hat undefiniertes Verhalten.

main.cpp

#include <iostream>
#include <ostream>
#include <string>

using namespace ::std::literals;

int main() { ::std::cout << 1/0 << "\n"s; }

Protokoll
Programmabbruch während der Ausführung
main.cpp

#include <iostream>
#include <ostream>
#include <string>

using namespace ::std::literals;

int main() { ::std::cout << 1/0. << "\n"s; }

Protokoll
inf

Was passiert tatsächlich bei der Ausführung eines Programms mit undefiniertem Verhalten? Dies läßt sich nicht vorhersagen! Alle der folgenden Möglichkeiten kommen in Frage:

Insbesondere kann es passieren, daß ein Programm mit undefiniertem Verhalten unter einer C++ -Implementation genau das tut, was der Programmierer erwartet, aber unter einer andere C++ -Implementation ein anderes Verhalten zeigt. Daher muß man sich beim Betrachten der in dieser Lektion gezeigten Beispiele darüber im Klaren sein, daß diese die Manifestation des undefinierten Verhaltens nur unter einer bestimmten Implementation  zeigen. Bei Verwendung einer anderen Implementation oder sogar bei Verwendung derselben Implementation unter anderen Umständen, kann sich das beobachtete Verhalten wieder unterscheiden. Insbesondere wird bei undefiniertem Verhalten keinesfalls immer eine Fehlermeldung ausgegeben, so daß es leider oft unbemerkt bleibt. Undefiniertes Verhalten ist eben grundsätzlich abstrakt und kann nur mit dem Verstand, aber nicht mit den Augen, gesehen werden!

Auch die Auswertung von »INT_MAX+1« hat undefiniertes Verhalten, da hier der Wertebereich des Datentyps »int« verlassen wird.

main.cpp

#include <climits> /* INT_MAX */
#include <iostream>
#include <ostream>
#include <string>

using namespace ::std::literals;

int main()
{ ::std::cout << INT_MAX << "\n"s;
::std::cout << INT_MAX+1 << "\n"s; }

Protokoll
2147483647
-2147483648

(Zur Erkennung solcher „Rechenfehler“ gibt es verschiedene “safeint ”-Zusatzbibliotheken.)

Undefiniertes Verhalten ist kein Verhalten eines Programmes, sondern einer Ausführung  eines Programms. Wenn ein Programm beispielsweise eine Zahl einliest und dann durch diese Zahl dividiert, dann zeigt es undefiniertes Verhalten, wenn Null eingegeben wird. Wenn keine Null eingegeben wird, dann hat dies Ausführung auch kein undefiniertes Verhalten. Ein Programm sollte so geschrieben werden, daß die zu erwartenden Ausführungen nie undefiniertes Verhalten zeigen. Wenn beispielsweise durch irgendwelche Umstände sichergestellt ist, daß nie Null eingegeben wird, dann kann man durch eine eingegebene Zahl dividieren, ohne dabei undefiniertes Verhalten befürchten zu müssen.

Zitate *
“If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.”, C++ 2016, 5p4
“If the second operand of / or % is zero the behavior is undefined.”, C++ 2016, 5.6p4
“undefined behavior” “behavior for which this International Standard imposes no requirements” C++ 2016, 1.3.25
“However, if any such execution contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).” – C++ 2016, 1.9p5

Gründe für die Verwendung undefinierten Verhaltens

Undefiniertes Verhalten ist eines der zentralen Begriffe der C++ -Programmierung. Es ergibt sich unter anderem aus folgender Überlegung:

Ein Prozessor ist ein Gerät, welches ein übersetztes Programm ausführt. Eine Division im Quelltext wird in der Regel in ein Divisionskommando für den Prozessor übersetzt. Es gibt aber verschiedene Prozessoren, deren Verhalten bei einer Division sich in Details unterscheiden kann. Beispielsweise ist folgendes möglich:

Ein C++ -Programm soll nun für beide Prozessoren übersetzt werden. Wie sollte das Verhalten der Programmiersprache für eine Division durch 0 definiert werden?

Würde also ein spezielles Verhalten für C++  festgelegt werden, so würde das Programm unter einigen Umgebungen nur verlangsamt laufen.

Portabilität ⃗

Ein Programm ist portabel, wenn es unter jeder  C++ -Implementation das gewünschte Verhalten zeigt.

Unspezifiziertes Verhalten ⃗

Bei unspezifiziertem Verhalten, ist eine bestimmte Entscheidung nicht spezifiziert. Es wird oft eine kurze Liste von Möglichkeiten aufgezählt, und jede C++ -Implementation muß eine dieser Möglichkeiten umsetzen.

Die Verwendung unspezifizierten Verhaltens muß kein Fehler des Programmierers sein – etwa, wenn alle in Frage kommenden Möglichkeiten akzeptabel sind, beispielsweise, weil sie das Ergebnis einer Berechnung nicht beeinflussen.

Implementationsdefiniertes Verhalten ⃗

Implementationsdefiniertes Verhalten ist unspezifiziertes Verhalten, das durch jede Implementation in ihrer Dokumentation festgelegt werden muß.

Seine Verwendung muß nicht unbedingt ein Fehler des Programmierers sein, wenn er das Verhalten einer bestimmten Implementation kennt und wünscht. Allerdings sind Programme mit implementationsdefiniertem Verhalten im allgemeinen nicht portabel.

Warum hat die Division durch Null kein unspezifiziertes oder implementations-definiertes Verhalten? ⃗

Die Division durch Null könnte auch als „unspezifiziertes“ oder „implementations-definiertes“ Verhalten definiert werden. Dies würde bedeuten, daß das Verhalten von der Implementation abhängt und die Norm in der Regel die möglichen Verhaltensweisen auflistet.

Undefiniertes Verhalten gibt der Implementation jedoch zusätzliche Freiheiten:

Außerdem deckt das undefinierte Verhalten auch den Fall ab, daß ein Prozessor selber undefiniertes Verhalten zeigt.

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram724094 stefan_ram:724094 Undefiniertes Verhalten in C++ Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd724094, slrprddef724094, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/undefiniertes_c++