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


Quelle est la différence entre un comportement indéfini, non spécifié et implémenté en C et C ++?


434
2018-03-07 21:10


origine


Réponses:


Comportement non défini est l'un de ces aspects du langage C et C ++ qui peut surprendre les programmeurs venant d'autres langues (d'autres langages tentent 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 signaleront 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 vers la chaîne littérale "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 du standard C ++, il invoque comportement indéfini:

L'effet de la tentative de modification d'un littéral de chaîne est indéfini.

Je peux entendre les gens crier "Mais attendez, je peux compiler ce pas de 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, de sorte que la première tentative d'assignation aboutit à un vidage du noyau. "C'est exactement le problème du comportement indéfini. invoquer un comportement indéfini (même les démons nasaux) S'il y a un comportement "correct" selon votre modèle mental du langage, ce modèle est simplement faux: le standard C ++ a le seul vote, point.

D'autres exemples de comportement indéfini incluent l'accès à un tableau au-delà de ses limites, déréférencer le pointeur nul, accéder aux objets après la fin de leur vie ou écrire prétendument des expressions intelligentes comme i++ + ++i.

La section 1.9 de la norme C ++ mentionne également les deux frères moins dangereux du comportement indéfini, comportement non spécifié et 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 défini par l'implémentation (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 à ces égards.

Certains autres aspects et opérations de la machine abstraite sont décrits dans la présente Norme internationale comme non spécifié (par exemple, ordre d'évaluation des arguments d'une fonction). Lorsque cela est possible, la présente Norme internationale définit un ensemble de comportements autorisés. Ceux-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 indéfini (par exemple, l'effet du déréférencement du pointeur nul). [ Remarque: la présente Norme internationale n'impose aucune exigence relative au comportement des programmes contenant un comportement indéfini.-note de fin ]

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

Le comportement indéfini admissible s'étend de ignorer complètement la situation avec des résultats imprévisibles, de se comporter lors de la traduction ou de l'exécution du programme d'une manière documentée caractéristique de l'environnement (avec ou sans émission d'un message de diagnostic), de terminer une traduction ou exécution (avec émission d'un message de diagnostic).

Que pouvez-vous faire pour éviter de tomber dans un comportement indéfini? Fondamentalement, vous devez lire bons livres en C ++ par des auteurs qui savent de quoi ils parlent. Visionner des tutoriels internet. Vis bullschildt.


324
2017-11-05 10:41



Eh bien, c'est fondamentalement un copier-coller droit de la norme

3.4.1 1 comportement défini par l'implémentation comportement non spécifié où   chaque documents de mise en œuvre comment le   le choix est fait

2 EXEMPLE Un exemple de   le comportement défini par l'implémentation est le   propagation du bit de poids fort quand   un entier signé est déplacé vers la droite.

3.4.3 1 comportement indéfini comportement, lors de l'utilisation d'un non portable ou erroné   construction de programme ou d'erreur   données pour lesquelles cette   La norme n'impose aucune exigence

2   NOTE Comportement indéfini possible   va d'ignorer la situation   complètement avec des résultats imprévisibles,   se comporter pendant la traduction ou   l'exécution du programme dans un document   manière caractéristique de la   environnement (avec ou sans le   émission d'un message de diagnostic),   mettre fin à une traduction ou une exécution   (avec l'émission d'un diagnostic   message).

3 EXEMPLE Un exemple de   comportement indéfini est le comportement sur   débordement d'entier.

3.4.4 1 comportement non spécifié utilisation d'une valeur non spécifiée ou d'un autre comportement   où cette Norme internationale   fournit deux ou plusieurs possibilités et   n'impose aucune exigence supplémentaire sur   qui est choisi dans tous les cas

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


81
2018-03-07 21:15



Une formulation facile 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 qu'ils doivent utiliser et fournissent une documentation sur 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 cela ne rentre pas dans char. Comment mettez-vous cette valeur dans char? en fait il n'y a pas moyen! Tout peut arriver, mais le plus judicieux serait de prendre le premier octet de cet int et de le mettre dans char. Il est juste faux de faire cela pour assigner 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?

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 ou non entraîner un comportement indéfini, mais certainement votre programme ne devrait pas produire un comportement non spécifié.


@eSKay Je pense que votre question mérite d'être corrigée pour clarifier davantage :)

pour fun(fun1(), fun2()); n'est pas le   comportement "implémentation définie"?   Le compilateur doit choisir un ou le   d'autres cours, après tout?

La différence entre implémentation-defined et unspecified, est que le compilateur est supposé 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 que sizeof(int) est de 4 pour une partie du programme et de 8 pour les autres. Contrairement à un 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 facilité si certains comportements non spécifiés étaient spécifiés. Jetez un oeil ici Dr. Stroustrup répond à cela:

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

OMI beaucoup trop de "choses" sont laissés   indéfini, non spécifié,   mise en œuvre, etc. Cependant,   c'est facile à dire et même à donner   des exemples de, mais difficile à réparer. Il   devrait également être noté qu'il n'est pas   tout ce qui est difficile à éviter la plupart des   les problèmes et produire portable   code.


50
2018-03-07 21:28



Du document de justification officiel C

Les termes non spécifié comportement, indéfini comportement, et défini par l'implémentation les comportements sont utilisés pour classer le résultat des programmes d'écriture dont les propriétés que le Standard ne décrit pas ou ne peut pas décrire complètement. L’adoption de cette catégorisation a pour but de permettre une certaine variété d’implémentations permettant à la qualité d’implémentation d’être une force active sur le marché et d’autoriser certaines extensions populaires, sans supprimer le cachet de conformité à la norme. L'annexe F de la norme répertorie les comportements qui appartiennent à l'une de ces trois catégories.

Comportement non spécifié donne à l'implémenteur une certaine latitude dans la traduction des programmes. Cette latitude ne va pas jusqu'à ne pas traduire le programme.

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

Défini par l'implémentation le comportement donne à l'implémenteur la liberté de choisir l'approche appropriée, mais exige 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 en fonction de la définition de l'implémentation. Les responsables de la mise en œuvre devraient garder à l'esprit ce critère lorsqu'ils décident de l'étendue de la définition d'une mise en œuvre. Comme pour les comportements non spécifiés, le simple fait de ne pas traduire la source contenant le comportement défini par l'implémentation n'est pas une réponse adéquate.


21
2018-01-23 18:46



Comportement non défini et comportement non spécifié a une courte description de celui-ci.

Leur résumé final:

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


8
2018-03-07 21:18



Historiquement, le comportement défini par l'implémentation et le comportement indéfini représentaient des situations dans lesquelles les auteurs de la norme s'attendaient à ce que les personnes écrivant des implémentations de qualité utilisent le jugement pour décider quelles garanties comportementales, le cas échéant, seraient utiles aux programmes dans le domaine d'application prévu. cibles prévues. Les besoins du code crunching haut de gamme sont assez différents de ceux du code système de bas niveau, et UB et IDB offrent aux rédacteurs du compilateur une flexibilité pour répondre à ces différents besoins. Aucune des deux catégories ne prescrit que les implémentations doivent se comporter de manière utile à des fins particulières, 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 qui corresponde à ce but. 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 ce que la mise en œuvre pourrait éventuellement faire serait 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 de compilateurs devraient définir des comportements utiles quand cela est possible que la norme les exige ou non) mais s'il pourrait y avoir des implémentations où la définition d'un comportement serait à la fois coûteuse et inutile. Un jugement selon lequel de telles implémentations peuvent exister ne signifie en aucune façon que l'on puisse juger de l'utilité de supporter un comportement défini sur d'autres plateformes.

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 où elles sont vitales. Au lieu de traiter UB comme une invitation à exercer un jugement raisonnable, les auteurs de compilateurs ont commencé à le traiter comme une excuse ne pas faire cela.

Par exemple, donnez 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 mise en œuvre à deux compléments n'aurait pas à déployer d'efforts que ce soit pour traiter l'expression v << pow comme un décalage de deux-complément sans se soucier de savoir si v était positif ou négatif.

La philosophie préférée parmi certains auteurs de compilateurs d'aujourd'hui, cependant, suggère que parce que v ne peut être négatif que si le programme va s’engager dans un comportement indéfini, il n’ya aucune raison pour que le programme coupe la plage négative de v. Même si le déplacement à gauche des valeurs négatives était auparavant 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 rédacteurs du compilateur devraient se sentir libres d'ignorer cela.


6
2018-04-16 03:32



Mise en œuvre définie-

Les implémenteurs souhaitent, devraient être bien documentés, la norme donne des choix mais est sûre de compiler

Non spécifié -

Identique à la mise en œuvre définie mais non documentée

Indéfini-

Tout peut arriver, prenez-en soin.


5
2018-03-17 07:11



Norme C ++ n3337 § 1.3.10 comportement défini par l'implémentation

comportement, pour une construction de programme bien formé et des données correctes,   dépend de la mise en œuvre et que chaque document de mise en œuvre

Parfois, le standard C ++ n’impose pas de comportement particulier à certaines constructions, mais dit plutôt qu’un comportement particulier, bien défini doit être choisi et décrit par 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   [Note: Un comportement non défini peut être attendu lorsque ce   La norme omet toute définition explicite du comportement ou lorsqu'un programme   utilise une construction erronée ou des données erronées. Autorisé non défini   le comportement varie d'ignorer complètement la situation avec   résultats imprévisibles, se comporter pendant la traduction ou le programme   exécution d'une manière documentée caractéristique de l'environnement   (avec ou sans émission d'un message de diagnostic), pour terminer   une traduction ou exécution (avec l'émission d'un diagnostic   message). De nombreuses constructions de programmes erronées n'engendrent pas indéfini   comportement; ils doivent être diagnostiqués. - note de fin]

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


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

comportement, pour une construction de programme bien formé et des données correctes,   dépend de la mise en œuvre [Note: La mise en œuvre n'est pas   requis pour documenter quel comportement se produit. La gamme de possible   les comportements sont généralement délimités par la présente Norme internationale. - fin   Remarque ]

C ++ Standard n'impose pas de comportement particulier à certaines constructions mais dit plutôt qu'un comportement particulier, bien défini doit être choisi ( bot pas nécessaire décrit) par 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 se comporte le programme.


4
2018-05-10 12:35



Il y a beaucoup de constructions qui devraient se comporter de manière utile et prévisible dans certains cas, mais on ne peut pratiquement pas le faire dans tous les cas sur toutes les implémentations. Souvent, l'ensemble des cas pour lesquels une construction doit être utilisable dépend de la plate-forme cible et du champ d'application. Étant donné que l'implémentation de différentes cibles et champs doit gérer différents ensembles de cas, la norme considère la question des cas à traiter comme un problème de qualité de mise en œuvre. En outre, parce que les auteurs de la norme ne voyaient pas la nécessité d'interdire aux implémentations «conformes» d'être de si mauvaise qualité qu'elles étaient inutiles, elles ne se donnaient pas la peine de mandater explicitement le comportement des instances auxquelles elles s'attendaient. même sans mandat.

Par exemple, le code:

struct foo {int x;} = {0};
int main(void)
{
  foo.x = 1;
  return foo.x-1;
}

utilise une lvalue de type int [c'est à dire. foo.x] pour accéder à la valeur stockée d'un objet de type struct foo, même si N1570 6.5p7 ne contient rien qui permettrait un objet de type struct foo être accessible sauf via une lvalue de type struct foo ou une lvalue d'un type de caractère, et la norme ne contient pas non plus de langage qui exempterait les expressions d'accès struct-member des exigences de 6.5p7.

Il est évident que tout compilateur qui ne peut pas gérer de simples expressions d'accès à struct-member doit être considéré comme étant d'une qualité exceptionnellement faible et probablement inapproprié pour une grande partie de n'importe quoi. Par conséquent, il devrait être raisonnable de s’attendre à ce que toute personne cherchant à produire une mise en œuvre de qualité soutienne une telle construction, que la norme le commande ou non. Pourvu que l'on puisse faire confiance aux rédacteurs du compilateur pour produire des compilateurs de qualité convenant aux fins prévues et pour connaître les objectifs pour lesquels leurs compilateurs sont ou ne conviennent pas, il n'y aurait aucune raison d'utiliser l'encre usagée Standard. essayer d'énoncer des choses qui devraient être évidentes. Beaucoup d'actions qui devraient avoir des comportements utilisables et prévisibles sont, en fait, un comportement indéfini parce que les auteurs de la norme ont fait confiance aux rédacteurs de compilateurs pour exercer un jugement raisonnable, plutôt que d'utiliser le comportement indéterminé comme excuse pour jeter un jugement par la fenêtre. .


0
2017-07-11 22:33