Namensräume
Varianten
Aktionen

Undefiniertes Verhalten

Von cppreference.com
< c‎ | Sprache

Der C-Sprachstandard legt das beobachtbare Verhalten von C-Programmen präzise fest, mit Ausnahme der folgenden Kategorien:

  • undefiniertes Verhalten – es gibt keine Einschränkungen für das Verhalten des Programms. Beispiele für undefiniertes Verhalten sind Speicherzugriffe außerhalb von Array-Grenzen, Überlauf vorzeichenbehafteter Ganzzahlen, Dereferenzierung eines Nullzeigers, Änderung desselben Skalars mehr als einmal in einem Ausdruck ohne Sequenzpunkte, Zugriff auf ein Objekt über einen Zeiger eines anderen Typs usw. Compiler sind nicht verpflichtet, undefiniertes Verhalten zu diagnostizieren (obwohl viele einfache Situationen diagnostiziert werden), und das kompilierte Programm muss nichts Sinnvolles tun.
  • nicht spezifiziertes Verhalten – zwei oder mehr Verhaltensweisen sind zulässig und die Implementierung ist nicht verpflichtet, die Auswirkungen jedes Verhaltens zu dokumentieren. Beispiele hierfür sind die Auswertungsreihenfolge, ob identische String-Literale unterschiedlich sind usw. Jedes nicht spezifizierte Verhalten führt zu einem von mehreren gültigen Ergebnissen und kann bei Wiederholung im selben Programm ein anderes Ergebnis liefern.
  • implementierungsdefiniertes Verhalten – nicht spezifiziertes Verhalten, bei dem jede Implementierung dokumentiert, wie die Auswahl getroffen wird. Beispiele: Anzahl der Bits in einem Byte oder ob eine vorzeichenbehaftete Ganzzahl-Rechtsverschiebung arithmetisch oder logisch ist.
  • lokalespezifisches Verhalten – implementierungsdefiniertes Verhalten, das von der aktuell gewählten Locale abhängt. Beispiel: ob islower für andere Zeichen als die 26 Kleinbuchstaben der lateinischen Schrift true zurückgibt.

(Hinweis: Streng konforme Programme hängen nicht von nicht spezifiziertem, undefiniertem oder implementierungsdefiniertem Verhalten ab)

Compiler sind verpflichtet, Diagnosemeldungen (Fehler oder Warnungen) für Programme auszugeben, die gegen C-Syntaxregeln oder semantische Einschränkungen verstoßen, auch wenn ihr Verhalten als undefiniert oder implementierungsdefiniert spezifiziert ist oder wenn der Compiler eine Spracherweiterung anbietet, die ihm erlaubt, ein solches Programm zu akzeptieren. Diagnosen für undefiniertes Verhalten sind andernfalls nicht erforderlich.

Inhalt

[bearbeiten] 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,

[bearbeiten] Vorzeichenüberlauf

int foo(int x)
{
    return x + 1 > x; // either true or UB due to signed overflow
}

kann kompiliert werden als (Demo)

foo:
        mov     eax, 1
        ret

[bearbeiten] Zugriff außerhalb der Grenzen

int table[4] = {0};
int exists_in_table(int v)
{
    // return 1 in one of the first 4 iterations or UB due to out-of-bounds access
    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

[bearbeiten] Nicht initialisierte Skalare

_Bool p; // uninitialized local variable
if (p) // UB access to uninitialized scalar
    puts("p is true");
if (!p) // UB access to uninitialized scalar
    puts("p is false");

kann die folgende Ausgabe produzieren (beobachtet mit einer älteren Version von gcc)

p is true
p is false
size_t f(int x)
{
    size_t a;
    if (x) // either x nonzero or UB
        a = 42;
    return a;
}

kann kompiliert werden als (Demo)

f:
        mov     eax, 42
        ret

[bearbeiten] Ungültige Skalare

int f(void)
{
    _Bool b = 0;
    unsigned char* p = (unsigned char*)&b;
    *p = 10;
    // reading from b is now UB
    return b == 0;
}

kann kompiliert werden als (Demo)

f:
        mov     eax, 11
        ret

[bearbeiten] Nullzeiger-Dereferenzierung

int foo(int* p)
{
    int x = *p;
    if (!p)
        return x; // Either UB above or this branch is never taken
    else
        return 0;
}
 
int bar()
{
    int* p = NULL;
    return *p;    // Unconditional UB
}

kann kompiliert werden als (Demo)

foo:
        xor     eax, eax
        ret
bar:
        ret

[bearbeiten] Zugriff auf einen an realloc übergebenen Zeiger

Wählen Sie clang, um die angezeigte 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

[bearbeiten] 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.

[bearbeiten] Referenzen

  • C23-Standard (ISO/IEC 9899:2024)
  • 3.4 Verhalten (S. TBD)
  • 4 Konformität (S. 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

[bearbeiten] Siehe auch

C++-Dokumentation für Undefiniertes Verhalten

[bearbeiten] Externe Links

1.  What Every C Programmer Should Know About Undefined Behavior #1/3
2.  What Every C Programmer Should Know About Undefined Behavior #2/3
3.  What Every C Programmer Should Know About Undefined Behavior #3/3
4.  Undefiniertes Verhalten kann zu Zeitreisen führen (unter anderem, aber Zeitreisen ist das verrückteste).
5.  Understanding Integer Overflow in C/C++
6.  Undefined Behavior and Fermat’s Last Theorem
7.  Fun with NULL pointers, part 1 (lokaler Exploit in Linux 2.6.30 verursacht durch UB wegen Nullzeiger-Dereferenzierung)