Schauen wir uns an, wie der C-Standard die Begriffe "Verhalten" und "undefiniertes Verhalten" definiert.
Verweise beziehen sich auf den Entwurf N1570 der Norm ISO C 2011; Mir sind keine relevanten Unterschiede in den drei veröffentlichten ISO C-Standards (1990, 1999 und 2011) bekannt.
Abschnitt 3.4:
Verhalten
äußeres Erscheinungsbild oder Handlung
Ok, das ist ein bisschen vage, aber ich würde argumentieren, dass eine bestimmte Aussage kein "Aussehen" und sicherlich keine "Aktion" hat, es sei denn, sie wird tatsächlich ausgeführt.
Abschnitt 3.4.3:
undefiniertes Verhaltensverhalten
bei Verwendung eines nicht portierbaren oder fehlerhaften Programmkonstrukts oder fehlerhafter Daten, für die diese Internationale Norm keine Anforderungen stellt
Es heißt " bei Verwendung " eines solchen Konstrukts. Das Wort "Verwendung" ist nicht durch den Standard definiert, daher greifen wir auf die übliche englische Bedeutung zurück. Ein Konstrukt wird nicht "verwendet", wenn es nie ausgeführt wird.
Es gibt einen Hinweis unter dieser Definition:
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 für die Umgebung charakteristischen dokumentierten Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit dem Ausgabe einer Diagnosemeldung).
Ein Compiler kann Ihr Programm also zur Kompilierungszeit ablehnen , wenn sein Verhalten nicht definiert ist. Meine Interpretation davon ist jedoch, dass dies nur möglich ist, wenn nachgewiesen werden kann, dass bei jeder Ausführung des Programms ein undefiniertes Verhalten auftritt. Was impliziert, denke ich, dass dies:
if (rand() % 2 == 0) {
i = i / 0;
}
das sicherlich kann nicht definiertes Verhalten hat, kann nicht bei der Kompilierung abgelehnt werden.
In der Praxis müssen Programme in der Lage sein, Laufzeitprüfungen durchzuführen, um zu verhindern, dass undefiniertes Verhalten aufgerufen wird, und der Standard muss dies zulassen.
Ihr Beispiel war:
if (0) {
i = 1/0;
}
Die Division wird niemals durch 0 ausgeführt. Eine sehr verbreitete Redewendung ist:
int x, y;
if (y != 0) {
x = x / y;
}
Die Division hat sicherlich undefiniertes Verhalten, wenn y == 0
, aber es wird nie ausgeführt, wenn y == 0
. Das Verhalten ist gut definiert und aus demselben Grund, aus dem Ihr Beispiel gut definiert ist: weil das potenzielle undefinierte Verhalten niemals tatsächlich auftreten kann.
(Es sei denn INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(ja, die Ganzzahldivision kann überlaufen), aber das ist ein separates Problem.)
In einem Kommentar (seitdem gelöscht) hat jemand darauf hingewiesen, dass der Compiler zur Kompilierungszeit konstante Ausdrücke auswerten kann. Was wahr ist, aber in diesem Fall nicht relevant, weil im Kontext von
i = 1/0;
1/0
ist kein konstanter Ausdruck .
Ein konstanter Ausdruck ist eine syntaktische Kategorie, die sich auf einen bedingten Ausdruck reduziert (der Zuweisungen und Kommaausdrücke ausschließt). Die Herstellung konstanter Ausdruck erscheint in der Grammatik nur in Zusammenhängen , die tatsächlich einen konstanten Ausdruck, wie Fall Etiketten erfordern. Also, wenn Sie schreiben:
switch (...) {
case 1/0:
...
}
dann 1/0
ist ein konstanter Ausdruck - und einer, der die Einschränkung in 6.6p4 verletzt: "Jeder konstante Ausdruck muss als Konstante ausgewertet werden, die im Bereich der darstellbaren Werte für seinen Typ liegt." Daher ist eine Diagnose erforderlich. Die rechte Seite einer Zuweisung erfordert jedoch keinen konstanten Ausdruck , sondern lediglich einen bedingten Ausdruck , sodass die Einschränkungen für konstante Ausdrücke nicht gelten. Ein Compiler kann jeden Ausdruck auswerten , dass es bei der Kompilierung in der Lage ist, aber nur , wenn das Verhalten das gleiche ist , wie wenn es während der Ausführung ausgewertet wurde (oder, im Rahmen if (0)
, nicht während der Ausführung ausgewertet ().
(Etwas, das genau wie ein konstanter Ausdruck aussieht, ist nicht unbedingt ein konstanter Ausdruck , genauso wie in x + y * z
der Sequenz aufgrund des Kontexts, in dem sie erscheint, x + y
kein additiver Ausdruck ist .)
Was bedeutet, dass die Fußnote in N1570 Abschnitt 6.6, die ich zitieren wollte:
Somit ist
static int i = 2 || 1 / 0;
der Ausdruck in der folgenden Initialisierung ein gültiger ganzzahliger konstanter Ausdruck mit dem Wert eins.
ist für diese Frage eigentlich nicht relevant.
Schließlich gibt es einige Dinge, die definiert sind, um undefiniertes Verhalten zu verursachen, bei denen es nicht darum geht, was während der Ausführung passiert. Anhang J, Abschnitt 2 der C-Norm (siehe auch den Entwurf von N1570 ) listet Dinge auf, die undefiniertes Verhalten verursachen, wie aus dem Rest der Norm hervorgeht. Einige Beispiele (ich behaupte nicht, dass dies eine vollständige Liste ist) sind:
- Eine nicht leere Quelldatei endet nicht mit einem Zeilenumbruchzeichen, dem nicht unmittelbar ein Backslash-Zeichen vorangestellt ist, oder endet mit einem Teilvorverarbeitungstoken oder -kommentar
- Die Token-Verkettung erzeugt eine Zeichenfolge, die der Syntax eines universellen Zeichennamens entspricht
- Ein Zeichen, das nicht im grundlegenden Quellzeichensatz enthalten ist, wird in einer Quelldatei gefunden, außer in einem Bezeichner, einer Zeichenkonstante, einem Zeichenfolgenliteral, einem Headernamen, einem Kommentar oder einem Vorverarbeitungstoken, das niemals in ein Token konvertiert wird
- Ein Bezeichner, Kommentar, Zeichenfolgenliteral, Zeichenkonstante oder Headername enthält ein ungültiges Multibyte-Zeichen oder beginnt und endet nicht im anfänglichen Verschiebungsstatus
- Dieselbe Kennung hat sowohl interne als auch externe Verknüpfungen in derselben Übersetzungseinheit
Diese speziellen Fälle sind Dinge , die ein Compiler könnte erkennen. Ich denke, ihr Verhalten ist undefiniert, weil das Komitee nicht allen Implementierungen dasselbe Verhalten auferlegen wollte oder konnte, und die Definition einer Reihe zulässiger Verhaltensweisen war die Mühe einfach nicht wert. Sie fallen nicht wirklich in die Kategorie "Code, der niemals ausgeführt wird", aber ich erwähne sie hier der Vollständigkeit halber.