Comment expliquer un comportement indéfini à tous les débutants?

Il existe une poignée de situations que la norme C ++ atsortingbue à un comportement non défini. Par exemple, si j’alloue avec new[] , puis essayez de libérer avec delete (not delete[] ) un comportement indéfinitout peut arriver – cela peut fonctionner, il risque de se bloquer brutalement, de corrompre quelque chose en silence et de planter un problème chronométré.

C’est tellement problématique d’expliquer que tout peut arriver en partie aux débutants. Ils commencent à “prouver” que “cela fonctionne” (car cela fonctionne vraiment sur l’implémentation C ++ qu’ils utilisent) et demandent “qu’est-ce qui pourrait ne pas être mal avec cela”? Quelle explication concise pourrais-je donner qui les inciterait à ne pas écrire un tel code?

“Félicitations, vous avez défini le comportement du compilateur pour cette opération. Je m’attends à ce que le rapport sur le comportement des 200 autres compilateurs existants dans le monde soit affiché sur mon bureau demain à 10 heures. Ne décevez pas. moi maintenant, ton avenir est prometteur! ”

Undefined signifie explicitement non fiable. Le logiciel devrait être fiable. Vous ne devriez pas avoir à dire grand chose d’autre.

Un étang gelé est un bon exemple de surface de marche non définie. Ce n’est pas parce que vous traversez une fois que vous devez append un raccourci à votre itinéraire papier, surtout si vous planifiez les quatre saisons.

Deux possibilités me viennent à l’esprit:

  1. Vous pouvez leur demander “juste parce que vous pouvez conduire sur l’autoroute dans le sens opposé à minuit et survivre, le feriez-vous régulièrement?”

  2. La solution la plus complexe pourrait être de mettre en place un compilateur / environnement d’exécution différent pour leur montrer comment il échoue de façon spectaculaire dans des circonstances différentes.

Citez simplement de la norme. S’ils ne peuvent pas accepter cela, ils ne sont pas des programmeurs C ++. Est-ce que les chrétiens nieraient la Bible? 😉

1.9 Exécution du programme

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

  2. Certains aspects et opérations de la machine abstraite sont décrits dans la présente Norme internationale en tant que définition d’implémentation (par exemple, sizeof(int) ). Ceux-ci constituent les parameters de la machine abstraite. Chaque mise en œuvre doit inclure une documentation décrivant ses caractéristiques et son comportement à cet égard . […]

  3. Certains autres aspects et opérations de la machine abstraite sont décrits dans la présente Norme internationale comme non spécifiés (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. […]

  4. Certaines autres opérations sont décrites dans la présente Norme internationale comme indéfinies (par exemple, l’effet du déréférencement du pointeur nul). [Remarque: la présente Norme internationale n’impose aucune exigence concernant le comportement des programmes contenant un comportement indéfini . —Fin note]

Vous ne pouvez pas obtenir plus clair que cela.

Je leur expliquerais que s’ils n’écrivaient pas le code correctement, leur prochaine évaluation de performance ne serait pas heureuse. C’est une “motivation” suffisante pour la plupart des gens.

Laissez-les essayer jusqu’à ce que leur code tombe en panne pendant le test. Alors les mots ne seront pas nécessaires.

Le fait est que les débutants (nous sums tous passés par là) ont un peu d’ego et de confiance en eux. Ça va. En fait, vous ne pourriez pas être programmeur si vous ne le faisiez pas. Il est important de les éduquer, mais pas moins de les soutenir et de ne pas entraver leur cheminement en sapant leur confiance en eux-mêmes. Soyez juste poli mais prouvez votre position avec des faits et non des mots. Seuls les faits et les preuves fonctionneront.

Remplacez silencieusement new, new [], supprimez et supprimez [] et voyez combien de temps il lui faut pour remarquer;)

En cas d’échec … dites-lui simplement qu’il a tort et dirigez-le vers la spécification C ++. Oh oui… et la prochaine fois, soyez plus prudent lorsque vous embauchez des employés pour vous assurer d’éviter les trous!

J’aime cette citation:

Comportement indéfini: il peut corrompre vos fichiers, formater votre disque ou envoyer un courrier haineux à votre patron.

Je ne sais pas à qui atsortingbuer cela (c’est peut-être de Effective C ++ )?

John Woods :

En bref, vous ne pouvez pas utiliser sizeof () sur une structure dont les éléments n’ont pas été définis, et si vous le faites, des démons peuvent s’échapper de votre nez.

“Les démons peuvent voler de votre nez” doit simplement faire partie du vocabulaire de chaque programmeur.

Plus précisément, parlez de la portabilité. Expliquez comment les programmes doivent souvent être portés sur différents systèmes d’exploitation, sans parler de différents compilateurs. Dans le monde réel, les ports sont généralement créés par des personnes autres que les programmeurs d’origine. Certains de ces ports sont même destinés à des périphériques intégrés, où il peut être extrêmement coûteux de découvrir que le compilateur a décidé différemment de votre hypothèse.

Tournez la personne dans un pointeur. Dites-leur qu’ils sont un pointeur sur un humain de classe et que vous appelez la fonction ‘RemoveCoat’. Quand ils désignent une personne en disant «RemoveCoat», tout va bien. Si la personne n’a pas de manteau, ne vous inquiétez pas – nous vérifions cela, tout ce que RemoveCoat fait réellement, c’est enlever la couche supérieure du vêtement (avec vérifications de la décence).

Maintenant, que se passe-t-il s’ils pointent quelque part au hasard et qu’ils disent RemoveCoat – s’ils pointent vers un mur, la peinture risque de se décoller, s’ils pointent vers un arbre, l’écorce risque de se détacher, les chiens peuvent se raser eux-mêmes, l’USS Enterprise peut baisser ses boucliers à un moment critique, etc.

Il n’existe aucun moyen de déterminer ce qui pourrait arriver si le comportement n’a pas été défini pour cette situation. Il s’agit d’un comportement non défini et doit être évité.

C ++ n’est pas vraiment un langage pour les dilletantes, et simplement énumérer quelques règles et les faire obéir sans poser de questions fera pour de terribles programmeurs; La plupart des choses les plus stupides que je vois, disent les gens, sont probablement liées à ce type de règles aveugles qui suivent / les avocats.

D’autre part, s’ils savent que les destructeurs ne seront pas appelés et qu’ils rencontreront peut-être d’autres problèmes, ils prendront soin de l’éviter. Et plus important encore, vous avez la possibilité de le déboguer s’ils le font par accident, mais également de vous rendre compte à quel sharepoint nombreuses fonctionnalités du C ++ peuvent être dangereuses.

Puisqu’il y a beaucoup de choses à craindre, aucun cours ou livre ne fera jamais quelqu’un maîsortingser le C ++ ou probablement même devenir si bon avec cela.

On serait …

“Cet” usage ne fait pas partie du langage. Si nous dirions que dans ce cas, le compilateur doit générer un code qui se bloque, il s’agirait alors d’une fonctionnalité, d’une sorte d’exigence pour le fabricant du compilateur. Les rédacteurs de la norme n’ont pas voulu donner de travail inutile sur des “fonctionnalités” qui ne sont pas supscopes. Ils ont décidé de ne faire aucune exigence comportementale dans de tels cas.

Il suffit de leur montrer Valgrind.

Comstackr et exécuter ce programme:

 #include  class A { public: A() { std::cout << "hi" << std::endl; } ~A() { std::cout << "bye" << std::endl; } }; int main() { A* a1 = new A[10]; delete a1; A* a2 = new A[10]; delete[] a2; } 

Au moins lors de l’utilisation de GCC, cela montre que le destructeur n’est appelé que pour l’un des éléments lors d’une suppression unique.

À propos de la suppression unique sur les tableaux POD. Pointez-les vers une FAQ C ++ ou demandez- leur d'exécuter leur code via cppcheck .

Un point qui n’a pas encore été mentionné à propos du comportement indéfini est que si la réalisation d’une opération entraîne un comportement indéfini, une implémentation conforme aux normes peut légitimement, peut-être dans un effort d’être «utile» ou pour améliorer l’efficacité, générer du code qui échouera si une telle opération ont été tentés. Par exemple, on peut imaginer une architecture multiprocesseur dans laquelle n’importe quel emplacement de mémoire peut être verrouillé, et tenter d’accéder à un emplacement verrouillé (sauf pour le déverrouiller) sera bloqué jusqu’à ce que l’emplacement en question soit déverrouillé. Si le locking et le délocking étaient très économiques (plausibles s’ils sont implémentés dans le matériel), une telle architecture pourrait être pratique dans certains scénarios multi-threading, car implémenter x++ tant que (lecture et locking x atomiquement; append un pour lire la valeur; délocking atomique et écrire x) garantirait que si deux threads exécutaient simultanément x++ , le résultat serait d’append deux à x. Si les programmes sont écrits pour éviter les comportements indéfinis, une telle architecture pourrait faciliter la conception de code multithread fiable sans nécessiter de grandes barrières de mémoire encombrantes. Malheureusement, une instruction comme *x++ = *y++; peut provoquer un blocage si x et y font tous deux référence au même emplacement de stockage et si le compilateur tente de pipeline le code sous la forme t1 = read-and-lock x; t2 = read-and-lock y; read t3=*t1; write *t2=t3; t1++; t2++; unlock-and-write x=t1; write-and-unlock y=t2; t1 = read-and-lock x; t2 = read-and-lock y; read t3=*t1; write *t2=t3; t1++; t2++; unlock-and-write x=t1; write-and-unlock y=t2; . Bien que le compilateur puisse éviter un blocage en évitant d’entrelacer les différentes opérations, cela risquerait de nuire à l’efficacité.

Activez malloc_debug et delete un tableau d’objects avec des destructeurs. free un pointeur à l’intérieur du bloc devrait échouer. Appelez-les tous ensemble et démontrez-le.

Vous devrez penser à d’autres exemples pour renforcer votre crédibilité jusqu’à ce qu’ils comprennent qu’ils sont des débutants et qu’il y a beaucoup à apprendre sur le C ++.

Parlez-leur des normes et de la façon dont les outils sont développés pour se conformer à ces normes. Tout ce qui est en dehors de la norme peut ou peut ne pas fonctionner, ce qui est UB.

Le fait que leur programme semble fonctionner ne garantit rien; le compilateur peut générer du code qui fonctionne (comment définissez-vous même “travail” lorsque le comportement correct n’est pas défini ?) les jours de semaine mais formate votre disque les week-ends. Ont-ils lu le code source à leur compilateur? Examiner leur sortie démontée?

Ou rappelez-leur simplement parce qu’il se trouve que “travailler” aujourd’hui n’est pas garanti qu’il fonctionne lorsque vous mettez à niveau votre version du compilateur. Dites-leur de s’amuser à trouver les insectes subtils qui en découlent.

Et vraiment, pourquoi pas ? Ils devraient fournir un argument justifiable pour utiliser un comportement indéfini, et non l’inverse. Pour quelle raison utiliser delete au lieu de delete[] autre que la paresse? (D’accord, il y a std::auto_ptr . Mais si vous utilisez std::auto_ptr avec un new[] tableau alloué new[] , vous devriez probablement utiliser un std::vector toute façon.)