À quel type de bogues les déclarations «goto» aboutissent-elles? Existe-t-il des exemples historiquement significatifs?

103

Je comprends cela, sauf pour rompre les boucles imbriquées dans des boucles; la gotodéclaration est éludée et critiquée comme un style de programmation sujette aux bogues, pour ne jamais être utilisée.

XKCD Alt Text: "Neal Stephenson trouve ça mignon de nommer son label" dengo ""
Voir la bande dessinée originale à l' adresse : http://xkcd.com/292/

Parce que j'ai appris cela tôt; Je n'ai vraiment aucune idée ou expérience des types de bugs gotoqui y conduisent. Alors de quoi parle-t-on ici:

  • Instabilité?
  • Code intempestif ou illisible?
  • Des vulnérabilités de sécurité?
  • Quelque chose d'autre entièrement?

À quel type de bogues les déclarations "goto" conduisent-elles réellement? Existe-t-il des exemples historiquement significatifs?

Akiva
la source
33
Avez-vous lu le court article original de Dijkstra sur ce sujet? (C'est une question Oui / Non, et toute réponse autre qu'un instant sans équivoque "oui" est un "non".)
John R. Strohm
2
Je présume qu'une alarme se déclenche lorsque survient une défaillance catastrophique. où vous ne reviendrez jamais. Comme une panne de courant, des périphériques ou des fichiers en train de disparaître. Certaines sortes de "on got goto" ... Mais je pense que la plupart des erreurs "presque catastrophiques" peuvent être gérées par des constructions "try - catch" de nos jours.
Lenne
13
Personne n'a encore mentionné le GOTO calculé. Quand la terre était jeune, BASIC avait des numéros de ligne. Vous pouvez écrire ce qui suit: 100 N = A * 100; GOTO N. Essayez de déboguer ça :)
mcottle
7
@ JohnR.Strohm J'ai lu le papier de Dijkstra. Il a été écrit en 1968 et suggère que le besoin de gotos pourrait être réduit dans les langages incluant des fonctionnalités avancées telles que les instructions et les fonctions de commutateur. Il a été écrit en réponse à des langages dans lesquels goto était la principale méthode de contrôle de flux et pouvait être utilisé pour passer à n’importe quel point du programme. Tandis que dans les langages développés après la rédaction de cet article, par exemple C, goto ne peut sauter qu'à des emplacements situés dans le même cadre d'empilement et n'est généralement utilisé que lorsque les autres options ne fonctionnent pas bien. (suite ...)
Ray
10
(... suite) Ses arguments étaient valables à l'époque, mais ne sont plus d'actualité, que goto soit une bonne idée ou non. Si nous voulons argumenter d'une manière ou d'une autre, des arguments doivent être formulés qui se rapportent aux langues modernes. Au fait, avez- vous lu la "Programmation structurée avec instructions goto" de Knuth?
Ray

Réponses:

2

Voici une façon de regarder ce que je n'ai pas encore vu dans les autres réponses.

C'est à propos de la portée. L'un des principaux piliers d'une bonne pratique de programmation consiste à garder votre champ d'activité étroit. Vous avez besoin d'une marge de manœuvre étroite, car vous n'avez pas la capacité mentale de superviser et de comprendre plus que quelques étapes logiques. Vous créez donc de petits blocs (qui deviennent chacun "une chose"), puis vous construisez avec ceux-ci pour créer des blocs plus grands qui deviennent une seule chose, etc. Cela permet de garder les choses gérables et compréhensibles.

Un goto gonfle efficacement la portée de votre logique à l'ensemble du programme. Ceci est sûr de vaincre votre cerveau pour tous les programmes, même les plus petits, ne couvrant que quelques lignes.

Vous ne pourrez donc plus savoir si vous avez commis une erreur, car il y a trop à prendre et à vérifier votre pauvre petite tête. C'est le vrai problème, les bugs ne sont qu'un résultat probable.

Martin Maat
la source
Goto non local?
Déduplicateur
thebrain.mcgill.ca/flash/capsules/experience_jaune03.html << L'esprit humain peut suivre en moyenne 7 choses à la fois. Votre raisonnement entre en ligne de compte dans cette limitation, car l’élargissement de la portée ajoutera bien plus à ce qui doit être surveillé. Ainsi, les types de bugs qui seront introduits sont probablement des oublis et des oublis. Vous proposez également une stratégie d'atténuation et une bonne pratique en général, consistant à "garder votre champ d'activité étroit". En tant que tel, j'accepte cette réponse. Bon travail Martin.
Akiva
1
À quel point cela est cool?! Parmi plus de 200 votes, gagnant avec seulement 1!
Martin Maat
68

Ce n'est pas que le gotosoit mauvais en soi. (Après tout, chaque instruction de saut dans un ordinateur est un goto.) Le problème est qu’il existe un style humain de programmation qui précède la programmation structurée, ce que l’on pourrait appeler une programmation en "organigramme".

Dans la programmation par organigramme (que les gens de ma génération ont apprise et qui a été utilisée pour le programme Apollo Moon), vous créez un diagramme avec des blocs pour l'exécution des instructions et des diamants pour les décisions, et vous pouvez les relier avec des lignes qui vont partout. (Ce qu'on appelle "code spaghetti".)

Le problème avec le code spaghetti est que vous, le programmeur, pourriez "savoir" que c'était juste, mais comment pourriez-vous le prouver , à vous-même ou à quelqu'un d'autre? En fait, cela pourrait en réalité avoir une mauvaise conduite, et votre connaissance que c'est toujours correct pourrait être fausse.

Vient ensuite la programmation structurée avec des blocs de début à la fin, pour, tandis que, si-sinon, et ainsi de suite. Ceux-ci avaient l'avantage que vous pouviez toujours faire quelque chose, mais si vous faisiez attention, vous pouvez être sûr que votre code est correct.

Bien sûr, les gens peuvent toujours écrire du code spaghetti même sans goto. La méthode courante consiste à écrire un while(...) switch( iState ){..., où différents cas définissent le iStatesur différentes valeurs. En fait, en C ou C ++, on pourrait écrire une macro pour le faire, et le nommer GOTO, de sorte que dire que vous n'utilisez pas gotoest une distinction sans différence.

Comme exemple de la façon dont la vérification de code peut empêcher l'utilisation illimitée goto, je suis tombé il y a longtemps sur une structure de contrôle utile pour les interfaces utilisateur à changement dynamique. Je l'ai appelé exécution différentielle . Il est entièrement universel Turing, mais sa preuve de correction dépend de la programmation structurée pur - pas goto, return, continue, breakou exceptions.

entrez la description de l'image ici

Mike Dunlavey
la source
49
La correction ne peut être prouvée que dans les programmes les plus triviaux, même lorsque la programmation structurée est utilisée pour les écrire. Vous pouvez seulement augmenter votre niveau de confiance. la programmation structurée accomplit cela.
Robert Harvey
23
@MikeDunlavey: À propos: "Méfiez-vous des bogues dans le code ci-dessus; je n'ai fait que le prouver, mais je ne l'ai pas essayé." staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf
Mooing Duck
26
@ RobertHarvey Ce n'est pas tout à fait vrai que les preuves de correction ne sont pratiques que pour des programmes triviaux. Des outils plus spécialisés que la programmation structurée sont toutefois nécessaires.
Mike Haskel
18
@MSalters: C'est un projet de recherche. Le but d'un tel projet de recherche est de montrer qu'ils pourraient écrire un compilateur vérifié s'ils en avaient les ressources. Si vous regardez leur travail actuel, vous verrez qu'ils ne sont tout simplement pas intéressés par la prise en charge des versions ultérieures de C, mais plutôt par l'extension des propriétés de correction qu'ils peuvent prouver. Et pour cela, peu importe qu'ils soient en charge de C90, C99, C11, Pascal, Fortran, Algol ou Plankalkül.
Jörg W Mittag
8
@martineau: Je me méfie de ces "phrases de boson", dans lesquelles les programmeurs s'accordent pour dire que c'est mauvais ou bon, parce qu'ils vont s'asseoir s'ils ne le font pas. Il doit y avoir plus de raisons fondamentales pour les choses.
Mike Dunlavey
63

Pourquoi est-ce que c'est dangereux?

  • gotone provoque pas l'instabilité par lui-même. Malgré environ 100 000 gotosecondes, le noyau Linux reste un modèle de stabilité.
  • gotoà lui seul ne devrait pas causer de failles de sécurité Cependant, dans certaines langues, le fait de le mélanger avec des blocs de gestion try/ catchexception pourrait conduire à des vulnérabilités, comme expliqué dans cette recommandation CERT . Les compilateurs C ++ traditionnels signalent et empêchent de telles erreurs, mais malheureusement, les compilateurs plus anciens ou plus exotiques ne le font pas.
  • gotoprovoque un code illisible et non maintenable. C'est ce qu'on appelle aussi le code spaghetti , parce que, comme dans une assiette à spaghetti, il est très difficile de suivre le flux de contrôle quand il y a trop de gotos.

Même si vous réussissez à éviter le code spaghetti et si vous n'utilisez que quelques gotos, ils facilitent tout de même les problèmes de type bugs et fuites de ressources:

  • La programmation de la structure de code, avec des blocs et des boucles ou des commutateurs clairement imbriqués, est facile à suivre; son flux de contrôle est très prévisible. Il est donc plus facile de s'assurer que les invariants sont respectés.
  • Avec une gotodéclaration, vous interrompez ce flux simple et les attentes. Par exemple, vous ne remarquerez peut-être pas qu'il vous reste encore des ressources à libérer.
  • Beaucoup gotodans des endroits différents peuvent vous envoyer à une cible unique. Il n'est donc pas évident de savoir avec certitude l'état dans lequel vous vous trouvez lorsque vous atteignez cet endroit. Le risque de faire des hypothèses erronées / non fondées est donc assez grand.

Informations complémentaires et citations:

C fournit la gotodéclaration infiniment abusable et les étiquettes vers lesquelles créer une branche. Formellement, cela goton’est jamais nécessaire et, dans la pratique, il est presque toujours facile d’écrire du code sans celui-ci. (...)
Néanmoins, nous suggérons quelques situations où les goto peuvent trouver une place. L'utilisation la plus courante consiste à abandonner le traitement dans certaines structures profondément imbriquées, telles que la rupture de deux boucles à la fois. (...)
Bien que nous ne soyons pas dogmatiques à ce sujet, il semble que les déclarations goto devraient être utilisées avec parcimonie, voire pas du tout .

  • James Gosling & Henry McGilton ont écrit dans leur livre blanc sur l’environnement de langage Java de 1995 :

    Pas d'autres déclarations Goto
    Java n'a pas de déclaration goto. Les études ont montré que goto est (souvent) utilisé plus souvent que simplement «parce que c'est là». L'élimination de goto a conduit à une simplification du langage (...) Des études sur environ 100 000 lignes de code C ont permis de déterminer qu'environ 90% des instructions de goto étaient utilisées uniquement pour obtenir l'effet de la rupture de boucles imbriquées. Comme mentionné ci-dessus, la pause multi-niveaux et continuer à supprimer la plupart des besoins pour les déclarations goto.

  • Bjarne Stroustrup définit goto dans son glossaire en ces termes attrayants:

    goto - l'infâme goto. Principalement utile dans le code C ++ généré par la machine.

Quand peut-on utiliser?

Comme K & R, je ne suis pas dogmatique à propos de gotos. J'admets qu'il y a des situations où aller se détendre peut être facilité.

Généralement, en C, goto autorise la sortie de boucle multiniveau ou la gestion des erreurs nécessitant d'atteindre un point de sortie approprié qui libère / déverrouille toutes les ressources allouées jusqu'à présent (une allocation multiple en séquence signifie plusieurs libellés). Cet article quantifie les différentes utilisations du goto dans le noyau Linux.

Personnellement, je préfère l'éviter et en 10 ans de C, j'ai utilisé un maximum de 10 gotos. Je préfère utiliser des ifs imbriqués , que je pense plus lisibles. Lorsque cela conduirait à une imbrication trop profonde, je choisirais soit de décomposer ma fonction en parties plus petites, soit d'utiliser un indicateur booléen en cascade. Les compilateurs d'optimisation actuels sont suffisamment intelligents pour générer presque le même code que le même code goto.

L'utilisation de goto dépend fortement de la langue:

  • En C ++, l'utilisation correcte de RAII amène le compilateur à détruire automatiquement les objets qui sortent du domaine, de sorte que les ressources / le verrou soient nettoyés de toute façon, et qu'il n'y ait plus vraiment besoin de goto.

  • En Java , il n'y a pas besoin de goto (voir la citation de l' auteur de Java ci - dessus et cette excellente pile réponse Overflow ): le collecteur d'ordures qui nettoie le désordre, break, continueet try/ catchcouvrir la gestion des exceptions tous les cas où gotopourrait être utile, mais dans un monde plus sûr et mieux manière. La popularité de Java prouve que la déclaration goto peut être évitée dans un langage moderne.

Zoom sur la fameuse vulnérabilité SSL goto fail

Important Disclaimer: compte tenu de la discussion acharnée dans les commentaires, je tiens à préciser que je ne prétends pas que la déclaration goto est la seule cause de ce bogue. Je ne prétends pas que sans goto il n'y aurait pas de problème. Je veux juste montrer qu'un goto peut être impliqué dans un bug sérieux.

Je ne sais pas à combien de bugs sérieux sont liés gotodans l'histoire de la programmation: les détails ne sont souvent pas communiqués. Cependant, il y avait un fameux bogue Apple SSL qui affaiblissait la sécurité d'iOS. La déclaration qui a conduit à ce bogue était une gotodéclaration fausse .

Certains avancent que la cause principale du bogue n’était pas l’instruction goto en soi, mais un copier / coller incorrect, une indentation trompeuse, des accolades manquantes autour du bloc conditionnel ou peut-être les habitudes de travail du développeur. Je ne peux en confirmer aucun: tous ces arguments sont des hypothèses probables et une interprétation. Personne ne sait vraiment. ( entre temps, l'hypothèse d'une fusion qui a mal tourné, comme le suggère quelqu'un dans les commentaires, semble être un très bon candidat compte tenu d'autres incohérences d'indentation dans la même fonction ).

Le seul fait objectif est qu'un doublon a gotoconduit à quitter la fonction prématurément. En regardant le code, la seule autre déclaration qui aurait pu causer le même effet aurait été un retour.

L'erreur est en fonction SSLEncodeSignedServerKeyExchange()dans ce fichier :

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
        goto fail;
    if ((err =...) !=0)
        goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;  // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
    if (...)
        goto fail;

    ... // Do some cryptographic operations here

fail:
    ... // Free resources to process error

En effet, des accolades autour du bloc conditionnel auraient pu empêcher le bogue:
cela aurait entraîné soit une erreur de syntaxe lors de la compilation (et donc une correction), soit un goto redondant et inoffensif. À propos, GCC 6 serait en mesure de détecter ces erreurs grâce à son avertissement facultatif pour détecter une indentation incohérente.

Mais en premier lieu, tous ces gotos auraient pu être évités avec un code plus structuré. Donc, goto est au moins indirectement une cause de ce bogue. Il y a au moins deux manières différentes qui auraient pu l'éviter:

Approche 1: si clause ou ifs imbriqué

Au lieu de tester séquentiellement de nombreuses conditions d'erreur et d'envoyer chaque fois à une failétiquette en cas de problème, on aurait pu opter pour l'exécution des opérations cryptographiques dans une ifinstruction qui ne le ferait que s'il n'y avait pas de précondition erronée:

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
        (err = ...) == 0 ) &&
        (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
        ...
        (err = ...) == 0 ) )
    {
         ... // Do some cryptographic operations here
    }
    ... // Free resources

Approche 2: utiliser un accumulateur d'erreur

Cette approche est basée sur le fait que presque toutes les instructions ici appellent une fonction pour définir un errcode d'erreur et n'exécutent le reste du code que si err0 (c'est-à-dire une fonction exécutée sans erreur). Une alternative sûre et lisible est:

bool ok = true;
ok =  ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok =  ok && (err = NextFunction(...)) == 0;
...
ok =  ok && (err = ...) == 0;
... // Free resources

Ici, il n’ya pas un seul goto: aucun risque de sauter rapidement au point de sortie de la panne. Et visuellement, il serait facile de repérer une ligne mal alignée ou une ligne oubliée ok &&.

Cette construction est plus compacte. Il est basé sur le fait que dans C, la deuxième partie d'un logique and ( &&) n'est évaluée que si la première partie est vraie. En fait, l'assembleur généré par un compilateur optimiseur est presque équivalent au code original avec gotos: l'optimiseur détecte très bien la chaîne de conditions et génère du code qui, à la première valeur de retour non nulle, saute à la fin ( preuve en ligne ).

Vous pourriez même envisager une vérification de cohérence à la fin de la fonction qui pourrait, au cours de la phase de test, identifier des incohérences entre l'indicateur ok et le code d'erreur.

assert( (ok==false && err!=0) || (ok==true && err==0) );

Des erreurs telles qu'un ==0remplacement par inadvertance par un !=0connecteur logique ou des erreurs pourraient facilement être repérées pendant la phase de débogage.

Comme dit: je ne prétends pas que des constructions alternatives auraient évité tout bug. Je veux juste dire qu'ils auraient pu rendre le virus plus difficile à produire.

Christophe
la source
34
Non, le bogue Apple goto fail a été provoqué par un programmeur intelligent qui a décidé de ne pas inclure les accolades après une instruction if. :-) Ainsi, lorsque le prochain programmeur de maintenance (ou le même, la même différence) est arrivé et a ajouté une autre ligne de code qui n'aurait dû être exécuté que lorsque l'instruction if était évaluée à true, l'instruction a été exécutée à chaque fois. Bam, le bogue "goto fail", qui était un bogue de sécurité majeur . Mais cela n’a essentiellement rien à voir avec le fait qu’une déclaration goto a été utilisée.
Craig
47
Le bogue Apple "goto fail" était directement causé par une ligne de code dupliquée. L’effet particulier de ce bogue a été provoqué par le fait que cette ligne est une déclaration entre gotoaccolades if. Mais si vous suggérez qu'éviter gotorendra votre code à l'abri des effets des lignes dupliquées de manière aléatoire, j'ai de mauvaises nouvelles pour vous.
user253751
12
Vous utilisez un comportement d'opérateur booléen raccourci pour exécuter une instruction via un effet secondaire et prétendre que c'est plus clair qu'une ifinstruction? Des moments de plaisir. :)
Craig
38
Si au lieu de "goto fail" il y avait eu une déclaration "fail = true", exactement la même chose se serait produite.
gnasher729
15
Ce bug était dû à un développeur bâclé qui ne prêtait pas attention à ce qu'il faisait et ne lisait pas ses propres modifications de code. Ni plus ni moins. D'accord, peut-être ajouter quelques erreurs de test ici aussi.
Courses de légèreté en orbite
34

Le célèbre article de Dijkstra a été écrit à une époque où certains langages de programmation étaient en réalité capables de créer des sous-programmes comportant plusieurs points d'entrée et de sortie. En d'autres termes, vous pouvez littéralement sauter au milieu d'une fonction et en sortir à n'importe quel endroit de la fonction, sans appeler cette fonction ou en revenir de manière conventionnelle. C'est toujours vrai du langage d'assemblage. Personne n'a jamais prétendu qu'une telle approche était supérieure à la méthode structurée d'écriture de logiciel que nous utilisons maintenant.

Dans la plupart des langages de programmation modernes, les fonctions sont très spécifiquement définies avec une entrée et un point de sortie. Le point d'entrée est l'endroit où vous spécifiez les paramètres de la fonction et l'appelez, et le point de sortie est l'endroit où vous renvoyez la valeur résultante et continuez l'exécution à l'instruction suivant l'appel de la fonction d'origine.

Dans le cadre de cette fonction, vous devriez pouvoir faire ce que vous voulez, dans des limites raisonnables. Si mettre un ou deux goto dans la fonction rend plus claire ou améliore votre vitesse, pourquoi pas? L’intérêt d’une fonction est de séquestrer un peu de fonctionnalités clairement définies, de sorte que vous n’ayez plus à penser à la manière dont elle fonctionne en interne. Une fois que c'est écrit, il vous suffit de l'utiliser.

Et oui, vous pouvez avoir plusieurs instructions return dans une fonction. il y a toujours toujours une place dans une fonction correcte à partir de laquelle vous revenez (le côté arrière de la fonction, en gros). Ce n'est pas du tout la même chose que de sauter d'une fonction avec un goto avant qu'il ait une chance de revenir correctement.

entrez la description de l'image ici

Donc, il ne s'agit pas vraiment d'utiliser des gotos. Il s'agit d'éviter leurs abus. Tout le monde convient que vous pouvez créer un programme extrêmement compliqué à l'aide de gotos, mais vous pouvez également en faire autant en abusant de fonctions (c'est beaucoup plus facile en abusant de gotos).

Pour ce que cela vaut, depuis que je suis passé des programmes BASIC de type ligne à numéro à la programmation structurée à l'aide de langages Pascal et accolades, je n'ai jamais eu à utiliser un goto. La seule fois où j'ai été tenté d’en utiliser une, c’est de sortir rapidement d’une boucle imbriquée (dans les langues qui ne prennent pas en charge la sortie précoce de boucles à plusieurs niveaux), mais je peux généralement trouver une autre méthode plus propre.

Robert Harvey
la source
2
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft
11

À quel type de bogues les déclarations «goto» aboutissent-elles? Existe-t-il des exemples historiquement significatifs?

J'avais l'habitude d'utiliser des gotoinstructions lorsque j'écrivais des programmes BASIC en tant qu'enfant comme moyen simple d'obtenir des boucles for et While (le Commodore 64 BASIC ne disposait pas de boucles en boucle et j'étais trop immature pour apprendre la syntaxe et l'utilisation appropriées de la boucle for ). Mon code était souvent trivial, mais tout problème de boucle pouvait être immédiatement attribué à mon utilisation de goto.

J'utilise maintenant principalement Python, un langage de programmation de haut niveau qui a déterminé qu'il n'avait pas besoin de goto.

Lorsque Edsger Dijkstra a déclaré « Goto considéré comme nocif » en 1968 , il n'a pas donné une poignée d'exemples où les bugs liés pourraient être mis sur le compte goto, plutôt, il a déclaré que gotoétait inutile pour les langues de niveau supérieur et qu'il devrait être évité en faveur de ce nous considérons aujourd'hui le flux de contrôle normal: boucles et conditionnelles. Ses paroles:

L’utilisation incontrôlée de la fonction to to a pour conséquence immédiate qu’il devient extrêmement difficile de trouver un ensemble significatif de coordonnées permettant de décrire les progrès du processus.
[...]
Le déplacement de la déclaration telle qu'elle est est trop primitive; c'est trop une invitation à gâcher son programme.

Il a probablement eu des tas de bogues à chaque fois qu’il déboguait du code goto. Mais son document était une déclaration de position généralisée, étayée par une preuve gotoinutile pour les langages de niveau supérieur. Le bogue généralisé est qu’il est possible que vous ne puissiez pas analyser statiquement le code en question.

Le code est beaucoup plus difficile à analyser statiquement avec des gotoinstructions, en particulier si vous revenez en arrière dans votre flux de contrôle (ce que je faisais auparavant) ou à une section de code non liée. Le code peut et a été écrit de cette façon. Cela a été fait pour optimiser fortement les ressources informatiques très rares et donc les architectures des machines.

Un exemple apocryphe

Il y avait un programme de blackjack qu'un responsable a trouvé très optimisé mais impossible à "réparer" en raison de la nature du code. Elle a été programmée dans un code machine qui dépend fortement de la méthode gotos - je pense donc que cette histoire est tout à fait pertinente. C’est le meilleur exemple canonique auquel je puisse penser.

Un contre-exemple

Cependant, la source C de CPython (l’implémentation Python la plus courante et la plus utilisée) utilise des gotoinstructions très efficaces. Ils sont utilisés pour contourner les flux de contrôle non pertinents dans les fonctions pour arriver à la fin des fonctions, ce qui les rend plus efficaces sans perdre en lisibilité. Ceci respecte l'un des idéaux de la programmation structurée : avoir un seul point de sortie pour les fonctions.

Pour mémoire, je trouve que ce contre-exemple est assez facile à analyser statiquement.

Aaron Hall
la source
5
L’histoire de Mel que j’ai rencontrée portait sur une architecture étrange où (1) chaque instruction en langage machine effectue un goto après son fonctionnement et (2) il était terriblement inefficace de placer une séquence d’instructions dans des emplacements de mémoire consécutifs. Et le programme a été écrit dans un assemblage optimisé à la main et non dans un langage compilé.
@MilesRout Je pense que vous pourriez avoir raison, mais la page wikipedia sur la programmation structurée indique: "La déviation la plus courante, trouvée dans de nombreuses langues, est l'utilisation d'une instruction return pour la sortie anticipée d'un sous-programme. Il en résulte plusieurs points de sortie. , au lieu du seul point de sortie requis par la programmation structurée. " - Vous dites que c'est incorrect? Avez-vous une source à citer?
Aaron Hall
@ AaronHall Ce n'est pas le sens original.
Miles Rout
11

Lorsque le wigwam était un art architectural actuel, leurs constructeurs pouvaient sans aucun doute vous donner de judicieux conseils pratiques concernant la construction de wigwams, la façon de laisser la fumée s'échapper, etc. Heureusement, les constructeurs d'aujourd'hui peuvent probablement oublier la plupart de ces conseils.

Lorsque la diligence était un art de transport actuel, leurs conducteurs pourraient sans aucun doute vous donner de judicieux conseils pratiques concernant les chevaux de diligence, la façon de se défendre contre les grands bandits de route, etc. Heureusement, les automobilistes d'aujourd'hui peuvent également oublier la plupart de ces conseils.

Lorsque les cartes perforées constituaient un art de programmation courant, leurs praticiens pourraient également vous donner de judicieux conseils pratiques concernant l’organisation des cartes, la numérotation des relevés, etc. Je ne suis pas sûr que ce conseil soit très pertinent aujourd'hui.

Êtes-vous déjà assez âgé pour connaître la phrase "numéroter des déclarations" ? Vous n'avez pas besoin de le savoir, mais si vous ne le savez pas, vous ne connaissez pas le contexte historique dans lequel la mise en garde gotoétait principalement pertinente.

L'avertissement contre goton'est tout simplement pas très pertinent aujourd'hui. Avec une formation de base sur les boucles while / for et les appels de fonction, vous ne penserez même pas à en donner gototrès souvent. Quand vous y réfléchissez, vous avez probablement une raison, alors allez-y.

Mais ne pouvez-vous gotopas abuser de cette déclaration?

Réponse: bien sûr, il peut être utilisé de manière abusive, mais son utilisation est un problème minuscule en génie logiciel par rapport à des erreurs bien plus courantes, telles que l’utilisation d’une variable où une constante servira ou d’une programmation par copier-coller (sinon connu sous le nom négligence de refactor). Je doute que vous soyez beaucoup en danger. Sauf si vous utilisez longjmpou transférez de toute autre manière le contrôle vers du code éloigné, si vous pensez utiliser un gotoou vous souhaitez simplement l'essayer pour le plaisir, continuez. Ça ira.

Vous remarquerez peut-être le manque d' histoires d'horreur récentes dans lesquelles gotojoue le méchant. La plupart ou la totalité de ces histoires semblent avoir 30 ou 40 ans. Vous vous tenez sur des bases solides si vous considérez que ces histoires sont pour la plupart obsolètes.

thb
la source
1
Je vois que j'ai attiré au moins un vote négatif. Eh bien, il faut s'y attendre. D'autres avis défavorables peuvent venir. Cependant, je ne donnerai pas la réponse conventionnelle alors que des décennies d'expérience en programmation m'ont appris que, dans ce cas, la réponse conventionnelle s'avère fausse. C'est mon avis, en tout cas. J'ai donné mes raisons. Le lecteur peut décider.
thb
1
Votre message est bon, dommage que je ne puisse pas voter les commentaires.
Pieter B
7

Pour ajouter une chose aux autres excellentes réponses, avec des gotodéclarations, il peut être difficile de dire exactement comment vous êtes arrivé à un endroit donné du programme. Vous pouvez savoir qu'une exception s'est produite sur une ligne spécifique, mais s'il y a gotodes éléments dans le code, il n'y a aucun moyen de déterminer les instructions exécutées pour que cette exception provoque un état sans rechercher l'ensemble du programme. Il n'y a pas de pile d'appels ni de flux visuel. Il est possible qu'il existe une instruction distante de 1000 lignes qui vous a mis dans un mauvais état et qui a exécuté gotoune exception sur la ligne qui a généré une exception.

qfwfq
la source
1
Visual Basic 6 et VBA ont des erreurs de gestion des erreurs, mais si vous utilisez On Error Goto errh, vous pouvez utiliser Resumepour réessayer, Resume Nextpour ignorer la ligne incriminée et Erlpour savoir de quelle ligne il s'agissait (si vous utilisez des numéros de ligne).
Cees Timmerman
@CeesTimmerman J'ai très peu travaillé avec Visual Basic, je ne le savais pas. Je vais ajouter un commentaire à ce sujet.
Qfwfq
2
@CeesTimmerman: La sémantique VB6 de "resume" est horrible si une erreur se produit dans un sous-programme qui n'a pas son propre bloc sur erreur. Resumeva ré-exécuter cette fonction depuis le début et Resume Nextignorer tout ce qui ne l’a pas encore été.
Supercat
2
@ supercat Comme je l'ai dit, c'est douloureux. Si vous avez besoin de connaître la ligne exacte, numérotez-les tous ou désactivez temporairement le traitement des erreurs avec On Error Goto 0et manuellement Debug. Resumeest destiné à être utilisé après vérification Err.Numberet ajustements nécessaires.
Cees Timmerman
1
Je devrais noter que dans une langue moderne, gotone fonctionne qu'avec des lignes étiquetées, qui semblent avoir été remplacées par des boucles étiquetées .
Cees Timmerman
7

Les humains ont plus de mal à raisonner que les autres formes de contrôle de flux.

Programmer le bon code est difficile. Écrire des programmes corrects est difficile, déterminer si les programmes sont corrects est difficile, prouver qu'il est correct de le faire.

Obtenir du code pour faire vaguement ce que vous voulez est facile comparé à tout ce qui concerne la programmation.

goto résout certains programmes en obtenant du code pour faire ce que vous voulez. Cela ne facilite pas la vérification de l'exactitude, contrairement à ses alternatives.

Il existe des styles de programmation pour lesquels goto est la solution appropriée. Il y a même des styles de programmation où son jumeau diabolique, le comefrom, est la solution appropriée. Dans les deux cas, vous devez faire très attention à ce que vous l'utilisiez de manière compréhensible, et à de nombreuses vérifications manuelles pour vérifier que vous ne faites pas quelque chose de difficile pour en assurer l'exactitude.

À titre d’exemple, il existe une fonctionnalité de certaines langues appelée coroutines. Les coroutines sont des threads sans fil; état d'exécution sans thread pour l'exécuter. Vous pouvez leur demander de s’exécuter, et ils peuvent exécuter une partie d’eux-mêmes, puis se suspendre, ce qui leur rend le contrôle du flux.

Des routines "peu profondes" dans des langages sans prise en charge de coroutines (comme C ++ pré-C ++ 20 et C) sont possibles en utilisant un mélange de gotos et de gestion manuelle des états. Les coroutines "profondes" peuvent être réalisées à l'aide des fonctions setjmpet longjmpde C.

Il y a des cas où les coroutines sont si utiles que les écrire manuellement et avec soin en vaut la peine.

Dans le cas de C ++, ils sont jugés suffisamment utiles pour étendre le langage pour les prendre en charge. La gotogestion des états et manuelle est cachée derrière une couche d’abstraction sans coût, ce qui permet aux programmeurs de les écrire sans la difficulté de prouver leur fouillis d’objectifs, de void**construction manuelle / destruction d’état, etc.

Le goto se cache derrière une abstraction de niveau supérieur, comme whileou forou ifou switch. Ces abstractions de niveau supérieur sont plus faciles à prouver et à vérifier.

Si la langue en manquait certaines (comme certaines langues modernes, il manque des coroutines), vous pouvez utiliser l'un des motifs suivants: un motif qui ne correspond pas au problème ou utiliser la méthode goto.

Obtenir un ordinateur pour faire vaguement ce que vous voulez qu'il fasse dans les cas courants est facile . Écrire du code fiable et robuste est difficile . Goto aide le premier beaucoup plus que le second; d'où "goto considéré comme nuisible", car il s'agit d'un code de "travail" superficiel avec des bogues profonds et difficiles à détecter. Avec suffisamment d’effort, vous pouvez toujours créer du code avec "goto" de manière fiable, ce qui rend mal la règle absolue. mais en règle générale, c’est un bon choix.

Yakk
la source
1
longjmpest seulement légal en C pour dérouler la pile setjmpdans une fonction parent. (C'est comme sortir de plusieurs niveaux d'imbrication, mais pour les appels de fonction au lieu de boucles). longjjmpdans un contexte de setjmpfait dans une fonction qui est revenue depuis n'est pas légal (et je suppose que cela ne fonctionnera pas dans la pratique dans la plupart des cas). Je ne suis pas au courant de co-routines, mais de votre description d'une co-routine semblable à un fil de l' espace utilisateur, je pense qu'il aurait besoin de sa propre pile, ainsi qu'un contexte registre enregistré, et longjmpne vous donne inscrivez -saving, pas une pile séparée.
Peter Cordes
1
Oh, j'aurais dû googler : fanf.livejournal.com/105413.html explique comment créer de la pile pour la coroutine. (Ce qui, comme je le soupçonnais, est nécessaire en plus de simplement utiliser setjmp / longjmp).
Peter Cordes
2
@PeterCordes: les implémentations ne sont pas tenues de fournir un moyen permettant au code de créer une paire jmp_buff pouvant être utilisée comme coroutine. Certaines implémentations spécifient un moyen par lequel le code peut en créer un même si la norme ne les oblige pas à fournir une telle capacité. Je ne décrirais pas le code qui s'appuie sur de telles techniques en tant que "C standard", car dans de nombreux cas, un jmp_buff sera une structure opaque dont le comportement ne sera pas toujours cohérent entre différents compilateurs.
Supercat
6

Jetez un coup d’œil à ce code, disponible à l’ adresse http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html, qui faisait en fait partie d’une grande simulation du dilemme d’un prisonnier. Si vous avez vu l'ancien code FORTRAN ou BASIC, vous comprendrez que ce n'est pas si inhabituel.

C  Not nice rules in second round of tour (cut and pasted 7/15/93)
   FUNCTION K75R(J,M,K,L,R,JA)
C  BY P D HARRINGTON
C  TYPED BY JM 3/20/79
   DIMENSION HIST(4,2),ROW(4),COL(2),ID(2)
   K75R=JA       ! Added 7/32/93 to report own old value
   IF (M .EQ. 2) GOTO 25
   IF (M .GT. 1) GOTO 10
   DO 5 IA = 1,4
     DO 5 IB = 1,2
5  HIST(IA,IB) = 0

   IBURN = 0
   ID(1) = 0
   ID(2) = 0
   IDEF = 0
   ITWIN = 0
   ISTRNG = 0
   ICOOP = 0
   ITRY = 0
   IRDCHK = 0
   IRAND = 0
   IPARTY = 1
   IND = 0
   MY = 0
   INDEF = 5
   IOPP = 0
   PROB = .2
   K75R = 0
   RETURN

10 IF (IRAND .EQ. 1) GOTO 70
   IOPP = IOPP + J
   HIST(IND,J+1) = HIST(IND,J+1) + 1
   IF (M .EQ. 15 .OR. MOD(M,15) .NE. 0 .OR. IRAND .EQ. 2) GOTO 25
   IF (HIST(1,1) / (M - 2) .GE. .8) GOTO 25
   IF (IOPP * 4 .LT. M - 2 .OR. IOPP * 4 .GT. 3 * M - 6) GOTO 25
   DO 12 IA = 1,4
12 ROW(IA) = HIST(IA,1) + HIST(IA,2)

   DO 14 IB = 1,2
     SUM = .0
     DO 13 IA = 1,4
13   SUM = SUM + HIST(IA,IB)
14 COL(IB) = SUM

   SUM = .0
   DO 16 IA = 1,4
     DO 16 IB = 1,2
       EX = ROW(IA) * COL(IB) / (M - 2)
       IF (EX .LE. 1.) GOTO 16
       SUM = SUM + ((HIST(IA,IB) - EX) ** 2) / EX
16 CONTINUE

   IF (SUM .GT. 3) GOTO 25
   IRAND = 1
   K75R = 1
   RETURN

25 IF (ITRY .EQ. 1 .AND. J .EQ. 1) IBURN = 1
   IF (M .LE. 37 .AND. J .EQ. 0) ITWIN = ITWIN + 1
   IF (M .EQ. 38 .AND. J .EQ. 1) ITWIN = ITWIN + 1
   IF (M .GE. 39 .AND. ITWIN .EQ. 37 .AND. J .EQ. 1) ITWIN = 0
   IF (ITWIN .EQ. 37) GOTO 80
   IDEF = IDEF * J + J
   IF (IDEF .GE. 20) GOTO 90
   IPARTY = 3 - IPARTY
   ID(IPARTY) = ID(IPARTY) * J + J
   IF (ID(IPARTY) .GE. INDEF) GOTO 78
   IF (ICOOP .GE. 1) GOTO 80
   IF (M .LT. 37 .OR. IBURN .EQ. 1) GOTO 34
   IF (M .EQ. 37) GOTO 32
   IF (R .GT. PROB) GOTO 34
32 ITRY = 2
   ICOOP = 2
   PROB = PROB + .05
   GOTO 92

34 IF (J .EQ. 0) GOTO 80
   GOTO 90

70 IRDCHK = IRDCHK + J * 4 - 3
   IF (IRDCHK .GE. 11) GOTO 75
   K75R = 1
   RETURN

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

78 ID(IPARTY) = 0
   ISTRNG = ISTRNG + 1
   IF (ISTRNG .EQ. 8) INDEF = 3
80 K75R = 0
   ITRY = ITRY - 1
   ICOOP = ICOOP - 1
   GOTO 95

90 ID(IPARTY) = ID(IPARTY) + 1
92 K75R = 1
95 IND = 2 * MY + J + 1
   MY = K75R
   RETURN
   END

Beaucoup de problèmes ici vont bien au-delà de la déclaration de GOTO; Honnêtement, je pense que la déclaration de GOTO était un peu un bouc émissaire. Mais le flux de contrôle n'est absolument pas clair ici, et le code est mélangé de manière à rendre très flou ce qui se passe. Même sans ajouter de commentaires ou utiliser de meilleurs noms de variables, changer cela en une structure de bloc sans GOTO faciliterait grandement la lecture et le suivi.

prosfilaes
la source
4
Si vrai :-) Il vaut la peine de mentionner: les héritages FORTAN et BASIC n’avaient pas de structure de bloc, de sorte que si une clause THEN ne pouvait pas tenir sur une ligne, GOTO était la seule alternative.
Christophe
Des libellés au lieu de chiffres, ou du moins des commentaires sur les lignes numérotées, seraient d'une grande aide.
Cees Timmerman
De retour dans mes jours FORTRAN-IV: je me suis limité à utiliser GOTO pour implémenter des blocs IF / ELSE IF / ELSE, des boucles WHILE et BREAK et NEXT dans des boucles. Quelqu'un m'a montré les méfaits du code spaghetti et la raison pour laquelle vous devriez structurer pour l'éviter, même si vous devez implémenter vos structures de bloc avec GOTO. Plus tard, j'ai commencé à utiliser un pré-processeur (RATFOR, IIRC). Goto n'est pas intrinsèquement diabolique, c'est simplement une construction trop puissante de bas niveau mappée sur une instruction de branche assembleur. Si vous devez l'utiliser parce que votre langage est déficient, utilisez-le pour créer ou augmenter les structures de bloc appropriées.
nigel222
@CeesTimmerman: C'est le code FORTRAN IV de variétés de jardin. Les étiquettes de texte n'étaient pas prises en charge dans FORTRAN IV. Je ne sais pas si les versions les plus récentes de la langue les prennent en charge. Les commentaires sur la même ligne que le code n'étaient pas pris en charge dans la norme FORTRAN IV, bien que des extensions spécifiques au fournisseur puissent les prendre en charge. Je ne sais pas ce que dit le "standard" FORTRAN: j’ai abandonné Fortran il y a longtemps et je ne le manque pas. (Le dernier code FORTRAN que j'ai vu ressemble beaucoup au PASCAL du début des années 1970.)
John R. Strohm
1
@CeesTimmerman: extension spécifique au fournisseur. Le code en général est vraiment timide sur les commentaires. En outre, il existe une convention qui est devenue courante à certains endroits, consistant à ne mettre les numéros de ligne que dans les instructions CONTINUE et FORMAT, afin de faciliter l’ajout de lignes ultérieurement. Ce code ne suit pas cette convention.
John R. Strohm
0

L' encapsulation est l'un des principes de la programmation maintenable . Le fait est que vous vous connectez à un module / une routine / un sous-programme / un composant / un objet en utilisant une interface définie, et uniquement cette interface, et les résultats seront prévisibles (en supposant que l'unité a été testée de manière efficace).

Dans une même unité de code, le même principe s'applique. Si vous appliquez systématiquement les principes de la programmation structurée ou orientée objet, vous ne pourrez pas:

  1. créer des chemins inattendus dans le code
  2. arriver dans des sections de code avec des valeurs de variable indéfinies ou inadmissibles
  3. quitter un chemin du code avec des valeurs de variable indéfinies ou inadmissibles
  4. ne pas terminer une unité de transaction
  5. laissez des parties de code ou des données dans la mémoire qui sont effectivement mortes, mais non éligibles pour le nettoyage des ordures et leur réallocation, car vous n'avez pas signalé leur publication à l'aide d'une construction explicite qui appelle leur libération lorsque le chemin de contrôle du code quitte l'unité

Parmi les symptômes les plus courants de ces erreurs de traitement figurent les fuites de mémoire, l'accumulation de mémoire, les débordements de pointeur, les blocages, les enregistrements de données incomplets, les exceptions d'ajout / enregistrement / suppression d'enregistrements, les erreurs de page de mémoire, etc.

Les manifestations de ces problèmes observables par l'utilisateur incluent le verrouillage de l'interface utilisateur, la diminution progressive des performances, des enregistrements de données incomplets, l'impossibilité d'initier ou de terminer des transactions, des données corrompues, des pannes de réseau, des pannes de courant, des pannes de systèmes embarqués (du fait de la perte de contrôle des missiles perte de capacité de suivi et de contrôle dans les systèmes de contrôle de la circulation aérienne), défaillances de minuterie, etc., etc. Le catalogue est très complet.

jaxter
la source
3
Vous avez répondu sans mentionner gotomême une fois. :)
Robert Harvey
0

goto est dangereux car il est généralement utilisé là où il n’est pas nécessaire. Utiliser tout ce qui n'est pas nécessaire est dangereux, mais surtout , allez-y . Si vous google, vous rencontrerez beaucoup d'erreurs causées par goto, ce n'est pas une raison en soi de ne pas l'utiliser (des bugs surviennent toujours lorsque vous utilisez des fonctionnalités du langage car c'est intrinsèque à la programmation), mais certaines d'entre elles sont clairement très liées à goto utilisation.


Raisons d'utiliser / ne pas utiliser goto :

  • Si vous avez besoin d'une boucle, vous devez utiliser whileou for.

  • Si vous devez faire un saut conditionnel, utilisez if/then/else

  • Si vous avez besoin d'une procédure, appelez une fonction / méthode.

  • Si vous avez besoin de quitter une fonction, juste return.

Je peux compter sur mes doigts les endroits où j'ai vu gotoutilisé et utilisé correctement.

  • CPython
  • libKTX
  • probablement peu plus

Dans libKTX il y a une fonction qui a le code suivant

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

Maintenant, dans cet endroit gotoest utile parce que le langage est C:

  • nous sommes dans une fonction
  • nous ne pouvons pas écrire de fonction de nettoyage (car nous entrons dans une autre portée et rendre plus accessible l'état de la fonction appelant)

Ce cas d’utilisation est utile car en C, nous n’avons pas de classes, le moyen le plus simple de nettoyer consiste donc à utiliser un fichier goto.

Si nous avions le même code en C ++, plus besoin de goto:

class MyClass{
    public:

    void Function(){
        if(something)
            return Cleanup(); // invalid syntax in C#, but valid in C++
        if(bla)
            return Cleanup(); // invalid syntax in C#, but valid in C++
    }

    // access same members, no need to pass state (compiler do it for us).
    void Cleanup(){

    }



}

À quel genre de bugs cela peut-il conduire? N'importe quoi. Boucles infinies, mauvais ordre d’exécution, vis de pile ..

Un cas documenté est une vulnérabilité de SSL autorisant des attaques de type Man in the Middle causées par une mauvaise utilisation de goto: voici l'article

C'est une erreur de frappe, mais est passé inaperçu pendant un moment, si le code était structuré d'une autre manière, correctement testé, une telle erreur n'aurait peut-être pas été possible.

Développeur de jeu
la source
2
Il y a beaucoup de commentaires dans une réponse qui expliquent très clairement que l'utilisation de "goto" n'était absolument pas liée au bogue SSL - le bogue était dû à la duplication négligente d'une ligne de code. Il était donc exécuté une fois sous condition et inconditionnellement.
Gnasher729
Oui, j'attends un meilleur exemple historique d'un bug goto avant d'accepter. Comme dit; Peu importe ce qui était sur cette ligne de code; Le manque d'accolades l'aurait exécuté et aurait causé une erreur malgré tout. Je suis curieux cependant; Qu'est-ce qu'une "vis de pile"?
Akiva
1
Un exemple de code sur lequel j'ai travaillé précédemment dans PL / 1 CICS. Programme afficher un écran composé de cartes à une ligne. Lorsque l'écran a été retourné, il a trouvé le tableau de cartes qu'il avait envoyé. A utilisé les éléments de cela pour trouver l'index dans un tableau, puis utilisé cet index en tant qu'index de variables de tableau pour effectuer un GOTO afin qu'il puisse traiter cette carte individuelle. Si le tableau de cartes envoyées était incorrect (ou s'il avait été corrompu), il pourrait alors essayer de créer une erreur GOTO sur la mauvaise étiquette ou de provoquer une panne plus grave car elle avait accédé à un élément après la fin du tableau de variables d'étiquette. Réécrit pour utiliser la syntaxe de type de cas.
Kickstart