Namespaces
Variants

Undefined behavior

From cppreference.net

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

_Bool p; // nicht initialisierte lokale Variable
if (p) // UB-Zugriff auf nicht initialisierten Skalar
    puts("p is true");
if (!p) // UB-Zugriff auf nicht initialisierten Skalar
    puts("p is false");

Kann die folgende Ausgabe erzeugen (beobachtet mit einer älteren Version von gcc):

p ist true
p ist false
size_t f(int x)
{
    size_t a;
    if (x) // entweder x ungleich Null oder UB
        a = 42;
    return a;
}

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

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int *p = (int*)malloc(sizeof(int));
    int *q = (int*)realloc(p, sizeof(int));
    *p = 1; // UB access to a pointer that was passed to realloc
    *q = 2;
    if (p == q) // UB access to a pointer that was passed to realloc
        printf("%d%d\n", *p, *q);
}

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)