Quel est le comportement indéfini en C et C ++? Qu'en est-il du comportement non spécifié et du comportement défini par l'implémentation? Quelle est la différence entre eux?
530
Quel est le comportement indéfini en C et C ++? Qu'en est-il du comportement non spécifié et du comportement défini par l'implémentation? Quelle est la différence entre eux?
Réponses:
Le comportement indéfini est l'un de ces aspects du langage C et C ++ qui peut surprendre les programmeurs venant d'autres langages (d'autres langages essaient de mieux le cacher). Fondamentalement, il est possible d'écrire des programmes C ++ qui ne se comportent pas de manière prévisible, même si de nombreux compilateurs C ++ ne signalent aucune erreur dans le programme!
Regardons un exemple classique:
La variable
p
pointe vers le littéral de chaîne"hello!\n"
et les deux affectations ci-dessous tentent de modifier ce littéral de chaîne. Que fait ce programme? Selon le paragraphe 11 de la section 2.14.5 de la norme C ++, il invoque un comportement non défini :Je peux entendre des gens crier "Mais attendez, je peux compiler cela sans problème et obtenir la sortie
yellow
" ou "Que voulez-vous dire non défini, les littéraux de chaîne sont stockés dans la mémoire en lecture seule, donc la première tentative d'affectation entraîne un vidage de mémoire". C'est exactement le problème avec un comportement non défini. Fondamentalement, la norme permet à tout ce qui se passe une fois que vous invoquez un comportement indéfini (même les démons nasaux). S'il y a un comportement "correct" selon votre modèle mental de la langue, ce modèle est tout simplement faux; La norme C ++ a le seul vote, point final.D'autres exemples de comportement non défini incluent l'accès à un tableau au-delà de ses limites, le déréférencement du pointeur nul , l' accès aux objets après la fin de leur durée de vie ou l'écriture d' expressions prétendument intelligentes comme
i++ + ++i
.La section 1.9 de la norme C ++ mentionne également les deux frères moins dangereux d'un comportement indéfini, un comportement non spécifié et un comportement défini par l'implémentation :
Plus précisément, la section 1.3.24 stipule:
Que pouvez-vous faire pour éviter de rencontrer un comportement non défini? Fondamentalement, vous devez lire de bons livres C ++ par des auteurs qui savent de quoi ils parlent. Vissez des didacticiels Internet. Vis bullschildt.
la source
int f(){int a; return a;}
: la valeur dea
peut changer entre les appels de fonction.Eh bien, il s'agit essentiellement d'un simple copier-coller de la norme
la source
int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }
qu'un compilateur peut déterminer que puisque tous les moyens d'invoquer la fonction qui ne lancent pas les missiles invoquent un comportement indéfini, il peut rendre l'appellaunch_missiles()
inconditionnel.Une formulation simple pourrait peut-être être plus facile à comprendre que la définition rigoureuse des normes.
comportement défini par l'implémentation
Le langage dit que nous avons des types de données. Les fournisseurs du compilateur spécifient les tailles à utiliser et fournissent une documentation de ce qu'ils ont fait.
comportement indéfini
Vous faites quelque chose de mal. Par exemple, vous avez une très grande valeur dans un
int
qui ne rentre paschar
. Comment mettez-vous cette valeurchar
? en fait il n'y a aucun moyen! Tout pouvait arriver, mais le plus sensé serait de prendre le premier octet de cet entier et de le mettrechar
. Il est juste faux de faire cela pour affecter le premier octet, mais c'est ce qui se passe sous le capot.comportement non spécifié
Quelle fonction de ces deux est exécutée en premier?
La langue ne précise pas l'évaluation, de gauche à droite ou de droite à gauche! Par conséquent, un comportement non spécifié peut ou non entraîner un comportement non défini, mais votre programme ne doit certainement pas produire un comportement non spécifié.
@eSKay Je pense que votre question vaut la peine d'éditer la réponse pour clarifier davantage :)
La différence entre l'implémentation définie et non spécifiée, est que le compilateur est censé choisir un comportement dans le premier cas, mais il n'a pas à le faire dans le second cas. Par exemple, une implémentation doit avoir une et une seule définition de
sizeof(int)
. Donc, il ne peut pas dire quesizeof(int)
c'est 4 pour une partie du programme et 8 pour d'autres. Contrairement au comportement non spécifié, où le compilateur peut dire OK, je vais évaluer ces arguments de gauche à droite et les arguments de la fonction suivante sont évalués de droite à gauche. Cela peut arriver dans le même programme, c'est pourquoi on l'appelle non spécifié . En fait, C ++ aurait pu être rendu plus facile si certains des comportements non spécifiés avaient été spécifiés. Jetez un œil ici à la réponse du Dr Stroustrup à cela :la source
fun(fun1(), fun2());
le comportement n'est-il pas"implementation defined"
? Le compilateur doit choisir l'un ou l'autre cours, après tout?"I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"
je comprends que celacan
se produise. Est-ce vraiment le cas avec les compilateurs que nous utilisons de nos jours?Extrait du document officiel de justification C
la source
Comportement indéfini et comportement non spécifié en a une brève description.
Leur résumé final:
la source
Historiquement, le comportement défini par l'implémentation et le comportement non défini représentaient des situations dans lesquelles les auteurs de la norme s'attendaient à ce que les personnes qui rédigent des implémentations de qualité utilisent leur jugement pour décider quelles garanties comportementales, le cas échéant, seraient utiles pour les programmes dans le champ d'application prévu s'exécutant sur le cibles prévues. Les besoins du code de numérotation numérique haut de gamme sont assez différents de ceux du code des systèmes de bas niveau, et UB et IDB offrent aux rédacteurs du compilateur la flexibilité nécessaire pour répondre à ces différents besoins. Aucune des deux catégories n'oblige les implémentations à se comporter d'une manière qui soit utile à un but particulier, ou même à quelque fin que ce soit. Cependant, les implémentations de qualité qui prétendent convenir à un usage particulier devraient se comporter d'une manière convenant à cet objectif.si la norme l'exige ou non .
La seule différence entre le comportement défini par l'implémentation et le comportement indéfini est que le premier requiert que les implémentations définissent et documentent un comportement cohérent même dans les cas où rien de l'implémentation ne pourrait être utile . La ligne de démarcation entre eux n'est pas de savoir s'il serait généralement utile pour les implémentations de définir des comportements (les rédacteurs du compilateur devraient définir les comportements utiles lorsque cela est pratique, que la norme les y oblige ou non), mais s'il peut y avoir des implémentations où la définition d'un comportement serait simultanément coûteuse. et inutile . Un jugement selon lequel de telles implémentations pourraient exister n'implique en aucune façon, forme ou forme, un jugement sur l'utilité de prendre en charge un comportement défini sur d'autres plates-formes.
Malheureusement, depuis le milieu des années 1990, les rédacteurs de compilateurs ont commencé à interpréter le manque de mandats comportementaux comme un jugement selon lequel les garanties comportementales ne valent pas le coût, même dans les domaines d'application où elles sont vitales, et même sur les systèmes où elles ne coûtent pratiquement rien. Au lieu de traiter UB comme une invitation à exercer un jugement raisonnable, les rédacteurs du compilateur ont commencé à le traiter comme une excuse pour ne pas le faire.
Par exemple, étant donné le code suivant:
une mise en œuvre à deux compléments n'aurait aucun effort à déployer pour traiter l'expression
v << pow
comme un changement à deux compléments, sans égard au caractèrev
positif ou négatif.Cependant, la philosophie préférée de certains des auteurs de compilateurs actuels suggérerait que, comme
v
il ne peut être négatif que si le programme va s'engager dans un comportement indéfini, il n'y a aucune raison pour que le programme écrête la plage négative dev
. Même si le décalage à gauche des valeurs négatives était pris en charge sur chaque compilateur d'importance et qu'une grande partie du code existant repose sur ce comportement, la philosophie moderne interpréterait le fait que la norme dit que les valeurs négatives à gauche sont UB comme ce qui implique que les rédacteurs du compilateur devraient se sentir libres de l'ignorer.la source
<<
ait UB sur des nombres négatifs est un petit piège désagréable et je suis heureux de m'en souvenir!i+j>k
renvoie 1 ou 0 dans les cas où l'addition déborde, à condition qu'il n'ait pas d'autres effets secondaires , un compilateur peut être en mesure de faire des optimisations massives qui ne seraient pas possibles si le programmeur écrivait le code comme(int)((unsigned)i+j) > k
.Norme n3337 C du § 1.3.10 du comportement défini par l' implémentation
Parfois, C ++ Standard n'impose pas de comportement particulier à certaines constructions mais dit à la place qu'un comportement particulier et bien défini doit être choisi et décrit par une implémentation particulière (version de la bibliothèque). Ainsi, l'utilisateur peut toujours savoir exactement comment le programme se comportera même si Standard ne le décrit pas.
Norme C ++ n3337 § 1.3.24 comportement indéfini
Lorsque le programme rencontre une construction qui n'est pas définie selon la norme C ++, il est autorisé à faire ce qu'il veut faire (peut-être m'envoyer un e-mail ou peut-être vous envoyer un e-mail ou peut-être ignorer complètement le code).
Norme C ++ n3337 § 1.3.25 comportement non spécifié
C ++ Standard n'impose pas de comportement particulier à certaines constructions mais dit à la place qu'un comportement particulier et bien défini doit être choisi (le bot n'est pas nécessairement décrit ) par une implémentation particulière (version de la bibliothèque). Ainsi, dans le cas où aucune description n'a été fournie, il peut être difficile pour l'utilisateur de savoir exactement comment le programme se comportera.
la source
Mise en œuvre définie-
Non spécifié -
Indéfini-
la source
uint32_t s;
, étant donné cela , évaluer1u<<s
quands
est 33 pourrait peut-être donner 0 ou peut-être 2, mais ne rien faire de plus farfelu. Les compilateurs plus récents, cependant, l'évaluation1u<<s
peuvent amener un compilateur à déterminer que, parce qu'ils
devait avoir été inférieur à 32 auparavant, tout code avant ou après cette expression qui ne serait pertinent que s'ils
avait été supérieur ou égal à 32 peut être omis.