Namespaces
Variants

Undefined behavior

From cppreference.net

La norme du langage C spécifie précisément le comportement observable des programmes en langage C, à l'exception de ceux des catégories suivantes :

  • comportement indéfini - il n'y a aucune restriction sur le comportement du programme. Des exemples de comportement indéfini sont les accès mémoire en dehors des limites d'un tableau, le dépassement d'entier signé, le déréférencement de pointeur nul, la modification du même scalaire plus d'une fois dans une expression sans points de séquence, l'accès à un objet via un pointeur d'un type différent, etc. Les compilateurs ne sont pas tenus de diagnostiquer le comportement indéfini (bien que de nombreuses situations simples soient diagnostiquées), et le programme compilé n'est pas tenu de faire quoi que ce soit de significatif.
  • comportement non spécifié - deux ou plusieurs comportements sont permis et l'implémentation n'est pas tenue de documenter les effets de chaque comportement. Par exemple, ordre d'évaluation , si des littéraux de chaîne identiques sont distincts, etc. Chaque comportement non spécifié produit l'un d'un ensemble de résultats valides et peut produire un résultat différent lorsqu'il est répété dans le même programme.
  • comportement défini par l'implémentation - comportement non spécifié où chaque implémentation documente comment le choix est effectué. Par exemple, le nombre de bits dans un octet, ou si le décalage à droite des entiers signés est arithmétique ou logique.
  • comportement spécifique aux paramètres régionaux - comportement défini par l'implémentation qui dépend de la locale actuellement choisie . Par exemple, si islower renvoie vrai pour tout caractère autre que les 26 lettres latines minuscules.

(Note : Les programmes strictement conformes ne dépendent d'aucun comportement non spécifié, indéfini ou défini par l'implémentation)

Les compilateurs doivent émettre des messages de diagnostic (soit des erreurs, soit des avertissements) pour tout programme qui viole une règle de syntaxe C ou une contrainte sémantique, même si son comportement est spécifié comme indéfini ou défini par l'implémentation, ou si le compilateur fournit une extension de langage qui lui permet d'accepter un tel programme. Les diagnostics pour les comportements indéfinis ne sont pas requis autrement.

Table des matières

UB et optimisation

Parce que les programmes C corrects sont exempts de comportement indéfini, les compilateurs peuvent produire des résultats inattendus lorsqu'un programme qui présente réellement un UB est compilé avec l'optimisation activée :

Par exemple,

Dépassement signé

int foo(int x)
{
    return x + 1 > x; // soit vrai ou UB dû à un dépassement de capacité signé
}

peut être compilé comme ( démo )

foo:
        mov     eax, 1
        ret

Accès hors limites

int table[4] = {0};
int exists_in_table(int v)
{
    // retourne 1 dans l'une des 4 premières itérations ou UB en raison d'un accès hors limites
    for (int i = 0; i <= 4; i++)
        if (table[i] == v)
            return 1;
    return 0;
}

Peut être compilé comme ( démo )

exists_in_table:
        mov     eax, 1
        ret

Scalaire non initialisé

_Bool p; // variable locale non initialisée
if (p) // accès UB à un scalaire non initialisé
    puts("p is true");
if (!p) // accès UB à un scalaire non initialisé
    puts("p is false");

Peut produire la sortie suivante (observée avec une ancienne version de gcc) :

p est true
p est false
size_t f(int x)
{
    size_t a;
    if (x) // soit x non nul ou UB
        a = 42;
    return a;
}

Peut être compilé comme ( démo )

f:
        mov     eax, 42
        ret

Scalaire invalide

int f(void)
{
    _Bool b = 0;
    unsigned char* p = (unsigned char*)&b;
    *p = 10;
    // la lecture de b est maintenant UB
    return b == 0;
}

Peut être compilé en tant que ( démo )

f:
        mov     eax, 11
        ret

Déréférencement de pointeur nul

int foo(int* p)
{
    int x = *p;
    if (!p)
        return x; // Soit UB ci-dessus ou cette branche n'est jamais prise
    else
        return 0;
}
int bar()
{
    int* p = NULL;
    return *p;    // UB inconditionnel
}

peut être compilé comme ( démo )

foo:
        xor     eax, eax
        ret
bar:
        ret

Accès au pointeur passé à realloc

Choisissez clang pour observer le résultat affiché

#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);
}

Résultat possible :

12

Boucle infinie sans effets secondaires

Choisissez clang pour observer la sortie affichée

#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.");
}

Sortie possible :

Fermat's Last Theorem has been disproved.

Références

  • Norme C23 (ISO/CEI 9899:2024) :
  • 3.4 Comportement (p: TBD)
  • 4 Conformité (p: TBD)
  • Norme C17 (ISO/CEI 9899:2018) :
  • 3.4 Comportement (p: 3-4)
  • 4 Conformité (p: 8)
  • Norme C11 (ISO/IEC 9899:2011) :
  • 3.4 Comportement (p: 3-4)
  • 4/2 Comportement indéfini (p: 8)
  • Norme C99 (ISO/CEI 9899:1999) :
  • 3.4 Comportement (p: 3-4)
  • 4/2 Comportement indéfini (p: 7)
  • Norme C89/C90 (ISO/CEI 9899:1990) :
  • 1.6 DÉFINITIONS DES TERMES

Voir aussi

Documentation C++ pour Comportement indéfini

Liens externes

1. Ce que tout programmeur C devrait savoir sur le comportement indéfini #1/3
2. Ce que tout programmeur C devrait savoir sur le comportement indéfini #2/3
3. Ce que tout programmeur C devrait savoir sur le comportement indéfini #3/3
4. Le comportement indéfini peut entraîner des voyages dans le temps (entre autres, mais le voyage temporel est le plus insolite)
5. Comprendre le dépassement d'entier en C/C++
6. Comportement indéfini et dernier théorème de Fermat
7. Jeux avec les pointeurs NULL, partie 1 (exploit local dans Linux 2.6.30 causé par un CI dû à un déréférencement de pointeur nul)