web-dev-qa-db-fra.com

Comportement non défini, non spécifié et défini par l'implémentation

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?

498
Zolomon

Comportement indéfini est l’un des aspects des langages C et C++ qui peut surprendre les programmeurs venant d’autres langages (autres les langues essayent 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:

#include <iostream>

int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

La variable p pointe sur 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 la section 2.14.5, paragraphe 11 de la norme C++, il appelle comportement indéfini:

L’effet de tenter de modifier un littéral de chaîne n’est pas défini.

J'entends des gens crier "Mais attendez, je peux compiler cela sans problème et obtenir le résultat yellow" ou "Que voulez-vous dire par non défini? Les littéraux de chaîne sont stockés dans une mémoire en lecture seule. La première tentative d'affectation aboutit à un dépotoir ". C’est exactement le problème du comportement indéfini. Fondamentalement, la norme autorise tout ce qui se passe lorsque vous invoquez un comportement indéfini (même des démons nasaux). S'il existe un comportement "correct" selon votre modèle mental du langage, 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, déréférencement du pointeur null , accès aux objets après la fin de leur durée de vie ou écriture expressions prétendument intelligentes comme i++ + ++i.

La section 1.9 de la norme C++ mentionne également les deux frères moins dangereux du comportement non défini, le comportement non spécifié et le comportement défini par l'implémentation :

Les descriptions sémantiques de la présente Norme internationale définissent une machine abstraite non déterministe paramétrée.

Certains aspects et opérations de la machine abstraite sont décrits dans la présente Norme internationale en tant que défini par la mise en oeuvre (par exemple, sizeof(int)). Ceux-ci constituent les paramètres de la machine abstraite. Chaque mise en œuvre doit inclure une documentation décrivant ses caractéristiques et son comportement à cet égard.

Certains autres aspects et opérations de la machine abstraite sont décrits dans la présente Norme internationale sous la forme non spécifiée (par exemple, ordre d'évaluation des arguments d'une fonction). Dans la mesure du possible, la présente Norme internationale définit un ensemble de comportements autorisés. Celles-ci définissent les aspects non déterministes de la machine abstraite.

Certaines autres opérations sont décrites dans la présente Norme internationale sous la forme non définie (par exemple, l’effet du déréférencement du pointeur nul). [Note: La présente Norme internationale n'impose aucune exigence concernant le comportement des programmes contenant un comportement indéfini. fin note]

Plus précisément, la section 1.3.24 indique:

Le comportement non défini autorisé va de en ignorant complètement la situation avec des résultats imprévisibles , au comportement lors de la traduction ou de l'exécution du programme de manière documentée, caractéristique de l'environnement (avec ou sans l'émission d'un message de diagnostic), pour mettre fin à une traduction ou à une exécution (avec l'émission d'un message de diagnostic).

Que pouvez-vous faire pour éviter d'avoir un comportement indéfini? En gros, vous devez lire de bons livres en C++ par des auteurs qui savent de quoi ils parlent. Vissez les tutoriels internet. Vis bullschildt.

375
fredoverflow

Eh bien, ceci est fondamentalement un copier-coller direct de la norme

.4.1 1 comportement défini par la mise en oeuvre comportement non spécifié dans lequel chaque mise en oeuvre documente la manière dont le choix est effectué

2 EXEMPLE Un exemple de comportement défini par l'implémentation est la propagation du bit de poids fort lorsqu'un entier signé est décalé à droite.

.4. 1 comportement indéfini comportement, lors de l'utilisation d'un programme de construction non portable ou erroné ou de données erronées, pour lequel la présente Norme internationale n'impose aucune exigence

2 REMARQUE Le comportement non défini possible peut aller d’ignorer complètement la situation avec des résultats imprévisibles, de se comporter pendant la traduction ou l’exécution du programme de manière documentée, caractéristique de l’environnement (avec ou sans émission d’un message de diagnostic), en mettant fin à la traduction ou à l’exécution (avec l'émission d'un message de diagnostic).

3 EXEMPLE Un exemple de comportement non défini est le comportement lors du dépassement d'entier.

.4.4 1 comportement non spécifié utilisation d'une valeur non spécifiée ou autre comportement pour lequel la présente Norme internationale fournit deux possibilités ou plus et n'impose aucune exigence supplémentaire qui soit choisie en aucune circonstance

2 EXEMPLE Un exemple de comportement non spécifié est l'ordre dans lequel les arguments d'une fonction sont évalués.

92
AnT

Peut-être une formulation plus facile à comprendre serait-elle plus facile à comprendre que la définition rigoureuse des normes.

comportement défini par la mise en oeuvre
Le langage dit que nous avons des types de données. Les vendeurs de compilateur spécifient les tailles qu'ils doivent 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 pas dans char. Comment mettez-vous cette valeur dans char? en fait il n'y a aucun moyen! Tout peut arriver, mais le plus sensé serait de prendre le premier octet de cet int et de le mettre dans char. C'est juste une erreur de faire cela en assignant le premier octet, mais c'est ce qui se passe sous le capot.

comportement non spécifié
Quelle fonction de ces deux fonctions est exécutée en premier?

void fun(int n, int m);

int fun1()
{
  cout << "fun1";
  return 1;
}
int fun2()
{
  cout << "fun2";
  return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?

La langue ne spécifie pas l'évaluation, de gauche à droite ou de droite à gauche! Ainsi, un comportement non spécifié peut entraîner ou non un comportement non défini, mais votre programme ne doit certainement pas produire un comportement non spécifié.


@ eSKay Je pense que votre question mérite une édition de la réponse pour clarifier davantage :)

pour fun(fun1(), fun2()); le comportement n'est-il pas "défini par l'implémentation"? Le compilateur doit choisir l'un ou l'autre cours, après tout?

La différence entre défini et non spécifié par l'implémentation est que le compilateur est supposé choisir un comportement dans le premier cas, mais que ce n'est pas le cas dans le second. Par exemple, une implémentation doit avoir une et une seule définition de sizeof(int). Donc, on ne peut pas dire que sizeof(int) soit 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, le C++ aurait pu être facilité si certains comportements non spécifiés étaient spécifiés. Jetez un oeil ici à La réponse du Dr Stroustrup à cela :

On prétend que la différence entre ce qui peut être produit en donnant cette liberté au compilateur et le fait de demander une "évaluation ordinaire de gauche à droite" peut être significative. Je ne suis pas convaincu, mais avec d'innombrables compilateurs "profitant" de la liberté et de certaines personnes défendant avec passion cette liberté, un changement serait difficile et pourrait prendre des décennies pour pénétrer dans les coins les plus reculés des mondes C et C++. Je suis déçu que tous les compilateurs ne mettent pas en garde contre du code tel que ++ i + i ++. De même, l'ordre d'évaluation des arguments n'est pas spécifié.

Bien trop de "choses" à l’OMI restent indéfinies, non spécifiées, définies par la mise en œuvre, etc. Cependant, c’est facile à dire et même à donner des exemples, mais difficile à corriger. Il convient également de noter qu'il n'est pas si difficile d'éviter la plupart des problèmes et de produire un code portable.

55
AraK

Extrait du document officiel de justification C

Les termes non spécifié comportement, non défini comportement et défini par la mise en oeuvre comportement sont utilisés pour classer le résultat d'écriture de programmes dont les propriétés ne sont pas normalisées, ou ne peut pas, décrire complètement. L’adoption de cette catégorisation a pour objectif de permettre une certaine variété parmi les implémentations, ce qui permet à la qualité de l’implémentation de jouer un rôle actif sur le marché, ainsi que certaines extensions populaires sans supprimer le cachet de la conformité à la norme. L'annexe F de la norme répertorie les comportements relevant de l'une de ces trois catégories.

comportement non spécifié donne au réalisateur une certaine latitude dans la traduction des programmes. Cette latitude ne va pas jusqu'à échouer dans la traduction du programme.

comportement indéfini donne à l'auteur de la licence l'autorisation de ne pas intercepter certaines erreurs de programme difficiles à diagnostiquer. Il identifie également les zones d'extension de langage conforme possible: l'implémenteur peut augmenter le langage en fournissant une définition du comportement officiellement indéfini.

Le comportement de défini par l'implémentation laisse à un implémenteur la liberté de choisir l'approche appropriée, mais nécessite que ce choix soit expliqué à l'utilisateur. Les comportements désignés comme définis par l'implémentation sont généralement ceux dans lesquels un utilisateur peut prendre des décisions de codage significatives basées sur la définition d'implémentation. Les responsables de la mise en œuvre doivent garder à l'esprit ce critère lorsqu'ils déterminent l'étendue d'une définition de mise en œuvre. Comme pour le comportement non spécifié, le fait de ne pas traduire le code source contenant le comportement défini par l'implémentation ne constitue pas une réponse adéquate.

25

Comportement non défini par rapport à un comportement non spécifié en a une brève description.

Leur résumé final:

Pour résumer, un comportement non spécifié est généralement quelque chose qui ne devrait pas vous inquiéter, sauf si votre logiciel doit être portable. Inversement, un comportement indéfini est toujours indésirable et ne devrait jamais se produire.

9
Anders Abel

Dans le passé, 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 écrivant des implémentations qualité utilisent leur jugement pour décider quelles garanties comportementales seraient utiles, le cas échéant, pour les programmes du domaine d'application prévu s'exécutant sur le système. cibles visées. Les besoins du code de calcul des calculs 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 de compilateur une flexibilité leur permettant de répondre à ces différents besoins. Aucune de ces catégories ne stipule que les implémentations se comportent de manière utile pour un objectif particulier, ou même pour un objectif quelconque. Les implémentations de qualité qui prétendent convenir à un usage particulier doivent toutefois se comporter de manière appropriée, que la norme l'exige ou non.

La seule différence entre le comportement défini par l'implémentation et le comportement non défini est que le premier exige que les implémentations définissent et documentent un comportement cohérent même dans les cas où rien ne pourrait l'implémenter serait utile. La ligne de démarcation entre eux n'est pas de savoir s'il serait généralement utile que les implémentations définissent des comportements (les auteurs du compilateur doivent définir des comportements utiles lorsque le standard le requiert ou non) mais s'il peut y avoir des implémentations définissant un comportement serait à la fois coûteux et inutile. Juger que de telles implémentations peuvent exister ne signifie en aucune manière que l’utilité de supporter un comportement défini sur d’autres plates-formes soit jugée utile.

Malheureusement, depuis le milieu des années 90, les rédacteurs de compilateurs ont commencé à interpréter l'absence 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 pas pour le faire.

Par exemple, étant donné le code suivant:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

une implémentation en complément à deux ne devrait déployer aucun effort pour traiter l'expression v << pow comme un décalage en complément à deux, sans se soucier de savoir si v était positif ou négatif.

La philosophie préférée de certains rédacteurs de compilateurs actuels, cependant, suggèrerait que, parce que v ne peut être négatif que si le programme doit adopter un comportement indéfini, il n’ya aucune raison de demander au programme de découper la plage négative de v. Même si le décalage à gauche des valeurs négatives était généralement pris en charge par chaque compilateur significatif, 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 auteurs du compilateur devraient se sentir libres de l'ignorer.

8
supercat

Mise en œuvre définie-

Les développeurs souhaitent, doivent être bien documentés, standard donne des choix mais est sûr de compiler

Non spécifié -

Identique à celle définie par l'implémentation mais non documentée

Indéfini-

Tout peut arriver, prenez-en soin.

6
Suraj K Thomas

Norme C++ n3337 § 1.3.10 comportement défini par la mise en oeuvre

comportement, pour un programme bien formé construire et corriger les données, qui dépend de l'implémentation et que chaque implémentation documente

Parfois, C++ Standard n'impose pas de comportement particulier à certaines constructions mais indique à 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 se comportera le programme, même si Standard ne le décrit pas.


Norme C++ n3337 § 1.3.24 comportement indéfini

comportement pour lequel la présente Norme internationale n'impose aucune exigence [Remarque: Un comportement indéfini peut être attendu lorsque la présente Norme internationale omet toute définition explicite du comportement ou lorsqu'un programme utilise une construction ou des données erronées. Le comportement non défini autorisé va d'ignorer complètement la situation avec des résultats imprévisibles, de se comporter de manière documentée pendant l'exécution de la traduction ou du programme, de manière documentée caractéristique de l'environnement (avec ou sans émission d'un message de diagnostic), jusqu'à mettre fin à la traduction ou à l'exécution (avec la publication). d'un message de diagnostic). Beaucoup de constructions de programme erronées n'engendrent pas un comportement indéfini; ils doivent être diagnostiqués. - note de fin]

Lorsque le programme rencontre une construction qui n'est pas définie selon la norme C++, il est autorisé à faire ce qu'il veut (peut-être m'envoyer un email ou peut-être un email ou ignorer complètement le code).


Norme C++ n3337 § 1.3.25 comportement non spécifié

comportement, pour une construction de programme bien formée et des données correctes, qui dépend de l'implémentation [Remarque: l'implémentation n'est pas obligée de documenter le comportement qui se produit. La gamme de comportements possibles est généralement définie par la présente Norme internationale. - note de fin]

C++ Standard n'impose pas de comportement particulier à certaines constructions, mais indique qu'un comportement particulier, bien défini, doit être choisi (bot 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 à l'utilisateur de savoir exactement comment le programme se comportera.

5
4pie0