Undefined behavior
Der C-Sprachstandard legt präzise das beobachtbare Verhalten von C-Sprachprogrammen fest, mit Ausnahme der folgenden Kategorien:
- undefined behavior - es gibt keine Einschränkungen für das Verhalten des Programms. Beispiele für undefined behavior sind Speicherzugriffe außerhalb von Array-Grenzen, signed integer overflow, null pointer dereference, Änderung desselben Skalars more than once in einem Ausdruck ohne Sequenzpunkte, Zugriff auf ein Objekt durch einen Zeiger eines anderen Typs, etc. Compiler sind nicht verpflichtet, undefined behavior zu diagnostizieren (obwohl viele einfache Situationen diagnostiziert werden), und das kompilierte Programm ist nicht verpflichtet, etwas Sinnvolles zu tun.
- unspecified behavior - zwei oder mehr Verhaltensweisen sind erlaubt und die Implementierung ist nicht verpflichtet, die Auswirkungen jeder Verhaltensweise zu dokumentieren. Zum Beispiel, order of evaluation , ob identische string literals unterschiedlich sind, usw. Jedes nicht spezifizierte Verhalten führt zu einem von einer Reihe gültiger Ergebnisse und kann ein anderes Ergebnis produzieren, wenn es im selben Programm wiederholt wird.
- implementation-defined behavior - nicht näher spezifiziertes Verhalten, bei dem jede Implementierung dokumentiert, wie die Wahl getroffen wird. Zum Beispiel: Anzahl der Bits in einem Byte oder ob die Rechtsverschiebung bei vorzeichenbehafteten Ganzzahlen arithmetisch oder logisch ist.
- Lokalspezifisches Verhalten - implementierungsdefiniertes Verhalten, das von der aktuell gewählten Locale abhängt. Zum Beispiel, ob islower true für andere Zeichen als die 26 lateinischen Kleinbuchstaben zurückgibt.
(Hinweis: Streng konforme Programme hängen nicht von nicht spezifiziertem, undefiniertem oder implementierungsdefiniertem Verhalten ab)
Die Compiler müssen Diagnosemeldungen (entweder Fehler oder Warnungen) für alle Programme ausgeben, die gegen eine C-Syntaxregel oder semantische Einschränkung verstoßen, selbst wenn ihr Verhalten als undefiniert oder implementierungsdefiniert spezifiziert ist oder wenn der Compiler eine Spracherweiterung bereitstellt, die es ermöglicht, ein solches Programm zu akzeptieren. Diagnosen für undefiniertes Verhalten sind ansonsten nicht erforderlich.
Inhaltsverzeichnis |
UB und Optimierung
Da korrekte C-Programme frei von undefiniertem Verhalten sind, können Compiler unerwartete Ergebnisse liefern, wenn ein Programm, das tatsächlich UB aufweist, mit aktivierter Optimierung kompiliert wird:
Zum Beispiel,
Vorzeichenbehafteter Überlauf
int foo(int x) { return x + 1 > x; // entweder wahr oder UB aufgrund von vorzeichenbehaftetem Überlauf }
kann kompiliert werden als ( Demo )
foo: mov eax, 1 ret
Zugriff außerhalb der Grenzen
int table[4] = {0}; int exists_in_table(int v) { // gibt 1 in einer der ersten 4 Iterationen zurück oder UB durch Out-of-Bounds-Zugriff for (int i = 0; i <= 4; i++) if (table[i] == v) return 1; return 0; }
Kann kompiliert werden als ( Demo )
exists_in_table: mov eax, 1 ret
Nicht initialisierter Skalar
Kann die folgende Ausgabe erzeugen (beobachtet mit einer älteren Version von gcc):
p ist true p ist false
Kann kompiliert werden als ( Demo )
f: mov eax, 42 ret
Ungültiger Skalar
int f(void) { _Bool b = 0; unsigned char* p = (unsigned char*)&b; *p = 10; // Das Lesen von b ist jetzt UB return b == 0; }
Kann kompiliert werden als ( Demo )
f: mov eax, 11 ret
Nullzeiger-Dereferenzierung
int foo(int* p) { int x = *p; if (!p) return x; // Entweder UB oben oder dieser Zweig wird nie ausgeführt else return 0; } int bar() { int* p = NULL; return *p; // Unbedingte UB }
kann kompiliert werden als ( Demo )
foo: xor eax, eax ret bar: ret
Zugriff auf Zeiger, der an realloc übergeben wurde
Wählen Sie clang, um die gezeigte Ausgabe zu beobachten
Mögliche Ausgabe:
12
Endlosschleife ohne Nebeneffekte
Wählen Sie clang, um die angezeigte Ausgabe zu beobachten
#include <stdio.h> int fermat() { const int MAX = 1000; // Endless loop with no side effects is UB for (int a = 1, b = 1, c = 1; 1;) { if (((a * a * a) == ((b * b * b) + (c * c * c)))) return 1; ++a; if (a > MAX) { a = 1; ++b; } if (b > MAX) { b = 1; ++c; } if (c > MAX) c = 1; } return 0; } int main(void) { if (fermat()) puts("Fermat's Last Theorem has been disproved."); else puts("Fermat's Last Theorem has not been disproved."); }
Mögliche Ausgabe:
Fermat's Last Theorem has been disproved.
Referenzen
- C23-Standard (ISO/IEC 9899:2024):
-
- 3.4 Behavior (p: TBD)
-
- 4 Conformance (p: TBD)
- C17-Standard (ISO/IEC 9899:2018):
-
- 3.4 Verhalten (S: 3-4)
-
- 4 Konformität (S: 8)
- C11-Standard (ISO/IEC 9899:2011):
-
- 3.4 Verhalten (S: 3-4)
-
- 4/2 Undefiniertes Verhalten (S: 8)
- C99-Standard (ISO/IEC 9899:1999):
-
- 3.4 Verhalten (S. 3-4)
-
- 4/2 Undefiniertes Verhalten (S. 7)
- C89/C90 Standard (ISO/IEC 9899:1990):
-
- 1.6 DEFINITIONEN VON BEGRIFFEN
Siehe auch
|
C++-Dokumentation
für
Undefined behavior
|
Externe Links
| 1. | Was jeder C-Programmierer über Undefined Behavior wissen sollte #1/3 |
| 2. | Was jeder C-Programmierer über Undefined Behavior wissen sollte #2/3 |
| 3. | Was jeder C-Programmierer über Undefined Behavior wissen sollte #3/3 |
| 4. | Undefined Behavior kann Zeitreisen verursachen (unter anderem, aber Zeitreisen sind das Verrückteste) |
| 5. | Verständnis von Integer Overflow in C/C++ |
| 6. | Undefined Behavior und Fermats letzter Satz |
| 7. | Spaß mit NULL-Zeigern, Teil 1 (lokaler Exploit in Linux 2.6.30 verursacht durch UB aufgrund von Nullzeiger-Dereferenzierung) |