Disons que je vais compiler un code source C ++ mal écrit qui invoque un comportement indéfini, et donc (comme on dit) "tout peut arriver".
Du point de vue de ce que la spécification du langage C ++ juge acceptable dans un compilateur "conforme", "quoi que ce soit" dans ce scénario inclut le plantage du compilateur (ou le vol de mes mots de passe, ou autrement un mauvais comportement ou erreur au moment de la compilation) portée du comportement indéfini limité spécifiquement à ce qui peut arriver lorsque l'exécutable résultant s'exécute?
c++
language-lawyer
undefined-behavior
Jeremy Friesner
la source
la source
Réponses:
La définition normative du comportement indéfini est la suivante:
Bien que la note elle-même ne soit pas normative, elle décrit une gamme de comportements que les implémentations sont connues pour présenter. Donc, planter le compilateur (qui est la traduction se terminant brusquement), est légitime selon cette note. Mais en réalité, comme le dit le texte normatif, la norme ne place aucune limite pour l'exécution ou la traduction. Si une implémentation vole vos mots de passe, ce n'est pas une violation d'un contrat énoncé dans la norme.
la source
La plupart des types d'UB dont nous nous soucions habituellement, comme NULL-deref ou diviser par zéro, sont des UB d' exécution . La compilation d'une fonction qui provoquerait UB d'exécution si elle était exécutée ne doit pas provoquer le plantage du compilateur. À moins que cela puisse prouver que la fonction (et ce chemin à travers la fonction) sera définitivement exécutée par le programme.
(Deuxième réflexion: peut-être que je n'ai pas considéré l'évaluation requise par template / constexpr au moment de la compilation. Peut-être que UB pendant cela est autorisé à provoquer une bizarrerie arbitraire pendant la traduction même si la fonction résultante n'est jamais appelée.)
Le comportement lors de la traduction de la partie de la citation ISO C ++ dans la réponse de @ StoryTeller est similaire au langage utilisé dans la norme ISO C. C n'inclut pas les modèles ou
constexpr
l'évaluation obligatoire au moment de la compilation.Mais fait amusant : ISO C dit dans une note que si la traduction est terminée, elle doit l'être avec un message de diagnostic. Ou "se comporter lors de la traduction ... de manière documentée". Je ne pense pas que «ignorer complètement la situation» pourrait être interprété comme incluant l'arrêt de la traduction.
Ancienne réponse, rédigée avant que j'apprenne UB au moment de la traduction. C'est vrai pour runtime-UB, cependant, et donc potentiellement toujours utile.
Il n'y a pas une telle chose comme UB qui arrive au moment de la compilation. Il peut être visible pour le compilateur le long d'un certain chemin d'exécution, mais en termes C ++, cela ne s'est pas produit jusqu'à ce que l'exécution atteigne ce chemin d'exécution via une fonction.
Les défauts dans un programme qui rendent même impossible la compilation ne sont pas UB, ce sont des erreurs de syntaxe. Un tel programme n'est "pas bien formé" dans la terminologie C ++ (si j'ai mes standards corrects). Un programme peut être bien formé mais contenir UB. Différence entre un comportement indéfini et mal formé, aucun message de diagnostic requis
À moins que je ne comprenne mal quelque chose, ISO C ++ nécessite que ce programme se compile et s'exécute correctement, car l'exécution n'atteint jamais la division par zéro. (Dans la pratique ( Godbolt ), un bon compilateur juste faire executables de travail. Gcc / clang mettent en garde contre
x / 0
mais pas, même lors de l' optimisation. Mais de toute façon, nous essayons de dire à quel point faible ISO C ++ permet la qualité de la mise en œuvre soit. Donc , la vérification gcc / clang n'est guère un test utile autre que pour confirmer que j'ai écrit le programme correctement.)int cause_UB() { int x=0; return 1 / x; // UB if ever reached. // Note I'm avoiding x/0 in case that counts as translation time UB. // UB still obvious when optimizing across statements, though. } int main(){ if (0) cause_UB(); }
Un cas d'utilisation pour cela pourrait impliquer le préprocesseur C, ou des
constexpr
variables et des branchements sur ces variables, ce qui conduit à des absurdités dans certains chemins qui ne sont jamais atteints pour ces choix de constantes.Les chemins d'exécution qui provoquent une UB visible au moment de la compilation peuvent être supposés ne jamais être empruntés, par exemple un compilateur pour x86 pourrait émettre une
ud2
(cause d'exception d'instruction illégale) comme définition pourcause_UB()
. Ou dans une fonction, si un côté d'unif()
conduit à prouver UB , la branche peut être supprimée.Mais le compilateur doit toujours compiler tout le reste d'une manière saine et correcte. Tous les chemins qui ne rencontrent pas (ou ne peuvent pas être prouvés) UB doivent toujours être compilés vers asm qui s'exécute comme si la machine abstraite C ++ l'exécutait.
Vous pourriez affirmer que l'UB visible au moment de la compilation inconditionnelle dans
main
est une exception à cette règle. Ou autrement prouvable au moment de la compilation, que l'exécution commençant àmain
atteint en fait UB garanti.Je dirais toujours que les comportements légaux du compilateur incluent la production d'une grenade qui explose si elle est exécutée. Ou plus vraisemblablement, une définition de
main
cela consiste en une seule instruction illégale. Je dirais que si vous n'exécutez jamais le programme, il n'y a pas encore eu d'UB. Le compilateur lui-même n'est pas autorisé à exploser, IMO.Fonctions contenant des UB possibles ou prouvables à l'intérieur des branches
UB le long de n'importe quel chemin d'exécution donné recule dans le temps pour «contaminer» tout le code précédent. Mais en pratique, les compilateurs ne peuvent tirer parti de cette règle que lorsqu'ils peuvent réellement prouver que les chemins d'exécution mènent à UB visible au moment de la compilation. par exemple
int minefield(int x) { if (x == 3) { *(char*)nullptr = x/0; } return x * 5; }
Le compilateur doit créer un asm qui fonctionne pour tous les
x
autres que 3, jusqu'aux points oùx * 5
provoque un dépassement de capacité UB signé à INT_MIN et INT_MAX. Si cette fonction n'est jamais appelée avecx==3
, le programme ne contient bien sûr pas d'UB et doit fonctionner comme écrit.Nous aurions tout aussi bien pu écrire
if(x == 3) __builtin_unreachable();
en GNU C pour dire au compilateur que cex
n'est certainement pas 3.En pratique, il y a du code «champ de mines» partout dans les programmes normaux. par exemple, toute division par un entier promet au compilateur qu'il est différent de zéro. Tout pointeur déréf promet au compilateur qu'il n'est pas NULL.
la source
Que veut dire «légal» ici? Tout ce qui ne contredit pas le standard C ou C ++ est légal, selon ces standards. Si vous exécutez une déclaration
i = i++;
et que les dinosaures prennent le contrôle du monde, cela ne contredit pas les normes. Cela contredit cependant les lois de la physique, donc ça n'arrivera pas :-)Si un comportement non défini plante votre compilateur, cela ne viole pas la norme C ou C ++. Cela signifie cependant que la qualité du compilateur pourrait (et devrait probablement) être améliorée.
Dans les versions précédentes de la norme C, certaines instructions étaient des erreurs ou ne dépendaient pas d'un comportement indéfini:
char* p = 1 / 0;
L'attribution d'une constante 0 à un caractère * est autorisée. Autoriser une constante non nulle ne l'est pas. Puisque la valeur de 1/0 est un comportement indéfini, il s'agit d'un comportement indéfini que le compilateur doive ou non accepter cette instruction. (De nos jours, 1/0 ne répond plus à la définition d '«expression constante entière»).
la source