Undefiniertes Verhalten

QS-Informatik
Beteilige dich an der Diskussion!
Dieser Artikel wurde wegen inhaltlicher Mängel auf der Qualitätssicherungsseite der Redaktion Informatik eingetragen. Dies geschieht, um die Qualität der Artikel aus dem Themengebiet Informatik auf ein akzeptables Niveau zu bringen. Hilf mit, die inhaltlichen Mängel dieses Artikels zu beseitigen, und beteilige dich an der Diskussion! ()


Begründung: Der Einleitungssatz steht im Widerspruch zum englischen Artikel, welcher undefiniertes Verhalten von unspezifiziertem Verhalten abgrenzt.

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

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