Undefiniertes Verhalten

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Undefiniertes Verhalten beschreibt in der Informatik Code, dessen Verhalten nicht spezifiziert ist und deshalb von verschiedenen Implementierungen von Compilern willkürlich behandelt wird. 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, Compiler-Implementationen dürfen in diesem Fall aber Diagnosen ausstellen.

Beispiele[Bearbeiten | Quelltext bearbeiten]

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[Bearbeiten | Quelltext bearbeiten]

Wenn eine Operation vom Standard mit bestimmten Werten als undefiniert erklärt wird, darf der Compiler 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 bedingte Block darf vom Compiler entfernt werden, denn der Zeiger wurde bereits dereferenziert. Der Compiler nimmt 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[Bearbeiten | Quelltext bearbeiten]

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 initializierten 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 sich 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[Bearbeiten | Quelltext bearbeiten]

  1. What Every C Programmer Should Know About Undefined Behavior. Abgerufen am 16. November 2014.