Les Undefined Behaviors
Last updated
Last updated
Les Undefined Behaviors (UB) sont des situations où aucun comportement n'est imposé au compilateur. Il s'agit donc d'erreurs difficiles à découvrir puisqu'elles peuvent faire crash le programme, renvoyer des valeurs incorrectes silencieusement ou faire fonctionner correctement le programme.
Elles existent pour permettent aux compilateurs d'optimiser le code (aucune contrainte n'existe pour la situation donnée) et augmenter leur comptabilité (n'importe quel comportement est accepté). (2011), et encore bien d'autres en C++.
Par exemple, l'overflow des entiers signés est un UB :
Des overflows des nombres signés peuvent être définis avec l'option -fwrapv,
et ils peuvent faire crash le programme avec -ftrapv
.
Le comportement d'une UB est fortement lié à la plateforme, le compilateur ou les optimisations activées. Des UB peuvent donc rester invisibles jusqu'au moment où une optimisation les exploite pour faire dysfonctionner le code.
Les Unspecified Behaviors désignent des situations correctes où plusieurs solutions sont disponibles, et aucune n'est requise. Par exemple, l'ordre d'évaluation des fonctions est un Unspecified Behavior :
Les Implementation-defined Behaviors sont des comportements qui peuvent changer selon l'implémentation. Cependant, contrairement aux Unspecified Behaviors, chaque implémentation doit toujours adopter la même solution. Par exemple, la taille en mémoire d'un pointeur fait partie de cette catégorie :
L'overflow des entiers signés.
Lire un objet non initialisé.
Utiliser des pointeurs de types différents pointant vers le même objet.
Ne pas retourner de valeur dans une fonction ne retournant pas void.
Déréférencer un pointeur nul, ou vers un objet non existant (ou déjà détruit).
Manipuler le membre inactif d'une union (comportement défini en C).
Une optimisation classique effectuée par le compilateur consiste à partir du principe que les UB ne sont jamais atteints. Ainsi, lorsqu'une UB est créé, le compilateur peut partir du principe que le chemin contenant l'UB n'est jamais atteint :
Dans l'exemple ci-dessus, la boucle dépasse table et finit par accéder à de la mémoire qui ne nous appartient pas : c'est un UB. Donc, le compilateur peut partir du principe qu'on atteint jamais ce moment. En effet, si la fonction retourne 'true' avant d'atteindre l'UB, elle est correcte. LE compilateur peut donc remplacer toute la fonction par return true
.
On peut utiliser ceci à notre avantage :
Des fonctions peuvent avoir un UB selon leurs paramètres, comme sqrt(-1)
.
Voici qui illustre certains Undefined Behaviors communs :
de Stack Overflow liste quelques UB qu'il faut garder en tête. Les UB nécessitent une attention particulière pour être évitées, mais des outils permettent de les mitiger (comme des analyseurs statiques, ou qui modifie le programme à la compilation pour détecter des UB en cours d'exécution).
Des effets assez spectaculaires peuvent se produire selon le contexte et l'UB. Par exemple, dans le code généré. On dit parfois que les UB peuvent remonter le temps, ou faire sortir des démons volants de vos narines (nasal demons).