Was ist Undefiniertes Verhalten (Undefined Behavior)?
Verhalten, für dessen Ergebnis der Standard von C bzw. C++ keinerlei Anforderungen stellt. Dazu gehören der Überlauf vorzeichenbehafteter Ganzzahlen, Lese- und Schreibzugriffe außerhalb der Grenzen eines Arrays, die Dereferenzierung eines NULL-Zeigers, die Verwendung eines freigegebenen Bereichs und der Linksshift negativer Werte. Da der Compiler beim Optimieren annehmen darf, dass in einem korrekten Programm kein undefiniertes Verhalten auftritt, kann ein Programm zufällig wie erwartet funktionieren oder aber unerwartete Ergebnisse liefern, etwa weil je nach Optimierungseinstellung eine Prüfung verschwindet. Zu unterscheiden ist es vom implementierungsdefinierten Verhalten, das die Implementierung festlegt und dokumentiert.
Japanische Bezeichnung: 未定義動作
„Undefiniertes Verhalten (Undefined Behavior)“ kommt in 10 Übungsfragen aus 2 Themen vor, z. B. Grundlagen der Programmiersprache C, Compiler.
Fachgebiet: Grundlagen der Programmiersprache C
Typische Beispiele
Dazu gehören der Überlauf bei Operationen mit vorzeichenbehafteten Ganzzahlen (etwa INT_MAX + 1), Lese- und Schreibzugriffe auf ein Array außerhalb seiner Grenzen, die Verwendung des Werts einer nicht initialisierten automatischen Variable, Lesen oder Schreiben über einen NULL-Zeiger oder einen Dangling Pointer, die doppelte Freigabe desselben Bereichs, eine ganzzahlige Division durch 0 sowie Shifts um die Typbreite oder mehr und der Linksshift negativer Werte. Der Überlauf vorzeichenloser Ganzzahlen ist kein undefiniertes Verhalten; für ihn ist festgelegt, dass das Ergebnis auf den Rest der Division durch eine Zweierpotenz zurückfällt.
Zusammenhang mit der Compiler-Optimierung
Weil der Compiler annehmen darf, dass „kein undefiniertes Verhalten auftritt“, behandelt er x + 1 > x mitunter als stets wahr oder entfernt eine Prüfung p == NULL, die nach dem Lesen von *p steht. Ein Programm, das mit -O0 wie erwartet lief, kann daher nach dem Wechsel zu -O2 oder einer neueren Compilerversion nicht mehr funktionieren. Die Ursache ist dann kein Compilerfehler, sondern ein Programm, das die Voraussetzungen des Standards verletzt.
Häufige Fragen
Was ist der Unterschied zwischen undefiniertem und implementierungsdefiniertem Verhalten?
Beim implementierungsdefinierten Verhalten wählt die Implementierung ein bestimmtes Verhalten und dokumentiert es, etwa die Größe von int, ob char ein Vorzeichen hat oder das Ergebnis eines Rechtsshifts negativer Werte. Beim undefinierten Verhalten stellt der Standard keinerlei Anforderungen; das Ergebnis darf sich selbst bei derselben Implementierung je nach Situation unterscheiden. Portable Programme kennzeichnen die Stellen, die sich auf implementierungsdefiniertes Verhalten stützen, und lösen kein undefiniertes Verhalten aus.
Ist undefiniertes Verhalten unproblematisch, solange das Programm funktioniert?
Nein. Auch wenn es in der aktuellen Umgebung zufällig wie erwartet funktioniert, kann sich das Ergebnis schon ändern, wenn sich Optimierungseinstellungen, die Compilerversion oder der umgebende Code ändern. Auch aus Sicht der Sicherheit kann undefiniertes Verhalten wie ein Schreibzugriff außerhalb der Grenzen für Angriffe missbraucht werden.