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;
}
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 pour cause_UB()
. Ou dans une fonction, si un côté d'un if()
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 avec x==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 ce x
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.