Undefined behavior
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é
Peut produire la sortie suivante (observée avec une ancienne version de gcc) :
p est true p est false
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é
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
|