Undefiniertes Verhalten
Undefiniertes Verhalten beschreibt in der Informatik Code, dessen Verhalten nicht spezifiziert ist und deshalb von verschiedenen Implementierungen von Compilern unterschiedlich behandelt werden kann. Dies ist eine Eigenschaft einiger Programmiersprachen, wie beispielsweise C[1] oder C++. Die Semantik bestimmter Operationen ist in den Standards dieser Sprachen nicht definiert, wodurch eine Implementierung davon ausgehen kann, dass diese Operationen im Normalfall nicht vorkommen. Dadurch stimmt das Verhalten der Implementierung in jedem Fall mit den Standards der Sprache überein. Es ist die Aufgabe des Programmierers, nie Code zu schreiben, der undefiniertes Verhalten auslöst.
Beispiele
In C führt beispielsweise die Division durch Null zu undefiniertem Verhalten:
int f(int x) {
return x/0; // undefiniert
}
Ebenso die Dereferenzierung (Verfolgung) eines Nullzeigers:
int* p = NULL;
int i = *p; // undefiniert
Optimierungsmöglichkeiten
Wenn eine Operation vom Standard mit bestimmten Werten als undefiniert erklärt wird, darf der Compiler davon ausgehen, dass die ungültigen Werte niemals vorkommen. Dabei darf der Compiler diese Annahme auf folgende Operationen anwenden. Ein Beispiel wäre die Dereferenzierung eines Zeigers. Falls der Zeiger NULL
wäre, wäre es undefiniertes Verhalten.
int get_int(int* p) {
int i = *p; // Dereferenzierung -> p != NULL
if(p == NULL) {
return 42;
}
return i;
}
Der komplette if
-Block darf vom Compiler entfernt werden, denn der Zeiger wurde bei der i-Zuweisung bereits dereferenziert. Der Compiler nimmt daher an, dass der Zeiger nicht NULL
sein kann. Dies kann behoben werden, indem man beispielsweise den Zugriff auf den referenzierten Speicher nach dem Test verschiebt:
int get_int(int* p) {
// p darf hier alles sein
if(p == NULL) {
return 42;
}
int i = *p; // Dereferenzierung -> p != NULL
return i;
}
Außerdem darf Folgendes auch komplett entfernt werden:
if(p == NULL) {
int i = *p;
printf("Hello");
}
Unterkategorien
Es wird zwischen undefinierten Operationen und undefinierten Werten unterschieden. Während das Lesen durch einen Nullzeiger eine undefinierte Operation ist (möglicherweise zum Programmabsturz führt), resultiert das Lesen von nicht initialisiertem Speicher nur in einem undefinierten Wert. Wenn beispielsweise ein unbekannter Wert mit exklusiv-oder mit sich selbst verknüpft wird, ist er immer 0, somit ist Folgendes komplett legal:
int value; // Startwert von value nicht gesetzt
value ^= value; // exklusiv-oder Verknüpfung
// value ist nun definiert als 0
Dieses ähnelt dem Herunterzählen bis auf 0 (bei int
wären negative Werte möglich, die Verringerung des niedrigsten Wertes, den int
annehmen kann, ist eine undefinierte Operation):
unsigned value;
while(value != 0) { --value; }
Auf diese Weise wird in der Sprache Brainfuck eine Speicherzelle auf 0 gesetzt, der Code dafür ist [-]
.
Einzelnachweise
- What Every C Programmer Should Know About Undefined Behavior. Abgerufen am 16. November 2014.