web-dev-qa-db-fra.com

À quel genre de bogues les instructions "goto" conduisent-elles? Y a-t-il des exemples historiquement significatifs?

Je comprends que sauf pour sortir des boucles imbriquées dans les boucles; l'instruction goto est éludée et vilipendée comme un style de programmation sujet aux bogues, pour ne jamais être utilisée.

XKCD Texte alternatif: "Neal Stephenson pense que c'est mignon de nommer ses étiquettes 'dengo'"
Voir la bande dessinée originale sur: http://xkcd.com/292/

Parce que j'ai appris cela tôt; Je n'ai pas vraiment de connaissances ni d'expérience sur les types de bogues que goto mène réellement. Alors de quoi parle-t-on ici:

  • Instabilité?
  • Code incontrôlable ou illisible?
  • Des failles de sécurité?
  • Quelque chose d'autre entièrement?

À quel genre de bogues les instructions "goto" mènent-elles réellement? Y a-t-il des exemples historiquement significatifs?

105
Akiva

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

Il s'agit de portée. L'un des principaux piliers des bonnes pratiques de programmation est de maintenir votre champ d'application serré. Vous avez besoin d'une portée étroite parce que vous n'avez pas la capacité mentale de superviser et de comprendre plus que quelques étapes logiques. Vous créez donc de petits blocs (dont chacun devient "une chose"), puis vous construisez avec ceux-ci pour créer des blocs plus gros qui deviennent une seule chose, et ainsi de suite. Cela permet de garder les choses gérables et compréhensibles.

Un goto gonfle efficacement l'étendue de votre logique à l'ensemble du programme. C'est sûr de vaincre votre cerveau pour tout sauf les plus petits programmes ne couvrant que quelques lignes.

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

2
Martin Maat

Ce n'est pas que le goto est 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 "organigramme".

Dans la programmation des organigrammes (que les gens de ma génération ont appris et ont été utilisés pour le programme Apollo Moon), vous faites un diagramme avec des blocs pour les exécutions de déclaration et des diamants pour les décisions, et vous pouvez les connecter avec des lignes qui vont partout. (Ce qu'on appelle le "code spaghetti".)

Le problème avec le code spaghetti est que vous, le programmeur, pouviez "savoir" que c'était bien, mais comment pourriez-vous le prouver , à vous-même ou à quelqu'un d'autre ? En fait, il pourrait en fait y avoir un mauvais comportement potentiel, et le fait de savoir qu'il est toujours correct pourrait être faux.

Vint une programmation structurée avec des blocs début-fin, pour, pendant, si-sinon, et ainsi de suite. Ceux-ci avaient l'avantage que vous pouviez toujours y faire quoi que ce soit, mais si vous faisiez attention, vous pouviez être sûr que votre code était correct.

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

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

enter image description here

69
Mike Dunlavey

Pourquoi est-ce dangereux?

  • goto ne provoque pas d'instabilité en soi. Malgré environ 100 000 gotos, le noyau Linux est toujours un modèle de stabilité.
  • goto en soi ne devrait pas provoquer de failles de sécurité. Dans certains langages cependant, le mélanger avec des blocs de gestion d'exceptions try/catch pourrait entraîner des vulnérabilités comme expliqué dans ce recommandation CERT . Les compilateurs C++ courants marquent et empêchent de telles erreurs, mais malheureusement, les compilateurs plus anciens ou plus exotiques ne le font pas.
  • goto provoque un code illisible et non maintenable. Ceci est aussi appelé code spaghetti , car, 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 parvenez à éviter le code spaghetti et si vous n'utilisez que quelques gotos, ils facilitent toujours les bugs comme et les fuites de ressources:

  • Le code utilisant une programmation structurée, avec des blocs et des boucles ou des commutateurs imbriqués clairs, 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 instruction goto, vous cassez ce flux simple et vous cassez les attentes. Par exemple, vous ne remarquerez peut-être pas que vous devez encore libérer des ressources.
  • De nombreux goto à différents endroits peuvent vous envoyer vers une seule cible goto. Il n'est donc pas évident de savoir avec certitude dans quel état vous vous trouvez en arrivant à cet endroit. Le risque de faire des hypothèses erronées/non fondées est donc assez grand.

Informations supplémentaires et devis:

C fournit la déclaration et les étiquettes goto infiniment exploitables pour se ramifier. Formellement, le goto n'est jamais nécessaire, et dans la pratique, il est presque toujours facile d'écrire du code sans lui. (...)
Néanmoins, nous suggérerons 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 instructions goto devraient être utilisées avec modération, voire pas du tout .

  • James Gosling & Henry McGilton a écrit dans leur 1995 Java :

    Plus de déclarations Goto
    Java n'a aucune instruction goto. Des études ont montré que le goto est (mal) utilisé plus souvent qu'autrement "parce qu'il est là". L'élimination du goto a conduit à une simplification du langage (...) Des études sur environ 100 000 lignes de code C ont déterminé qu'environ 90% des instructions goto étaient utilisées uniquement pour obtenir l'effet de rupture des boucles imbriquées. Comme mentionné ci-dessus, l'interruption à plusieurs niveaux et la suppression continuent de supprimer la plupart des instructions goto.

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

    goto - le fameux goto. Principalement utile dans le code C++ généré par machine.

Quand est-ce que goto pourrait être utilisé?

Comme K&R, je ne suis pas dogmatique à propos des gotos. J'admets qu'il y a des situations où goto pourrait faciliter la vie.

En général, en C, goto autorise la sortie de boucle à plusieurs niveaux 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 (c'est-à-dire que l'allocation multiple en séquence signifie plusieurs étiquettes). 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é au maximum 10 gotos. Je préfère utiliser les ifs imbriqués, qui je pense sont 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 d'aujourd'hui sont suffisamment intelligents pour générer presque le même code que le même code avec goto.

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

  • En C++, une utilisation appropriée de RAII oblige le compilateur à détruire automatiquement les objets qui sortent de la portée, de sorte que les ressources/verrou seront nettoyés de toute façon, et aucun besoin réel de goto plus.

  • Dans Java il n'y a pas besoin de goto (voir la citation de l'auteur de Java ci-dessus et ceci excellente réponse Stack Overflow ): le garbage collector qui nettoie le désordre, break, continue et try/catch la gestion des exceptions couvre tous les cas où goto pourrait être utile, mais d'une manière plus sûre et meilleure. prouve que l'instruction goto peut être évitée dans une langue moderne.

Zoom sur la fameuse vulnérabilité SSL goto fail

Avertissement important: au vu de la discussion acharnée dans les commentaires, je tiens à préciser que je ne prétendez pas que l'instruction goto est la seule cause de ce bogue. Je ne prétends pas que sans goto il n'y aurait pas de bug. Je veux juste montrer qu'un goto peut être impliqué dans un bug grave.

Je ne sais pas combien de bugs sérieux sont liés à goto dans l'histoire de la programmation: les détails ne sont souvent pas communiqués. Cependant, il y avait un célèbre bogue SSL d'Apple qui affaiblissait la sécurité d'iOS. L'instruction qui a conduit à ce bogue était une instruction goto incorrecte.

Certains soutiennent que la cause première du bogue n'était pas la déclaration goto en soi, mais un mauvais copier/coller, une indentation trompeuse, des accolades bouclées manquantes autour du bloc conditionnel, ou peut-être les habitudes de travail du développeur. Je ne peux pas non plus en confirmer aucun: tous ces arguments sont des hypothèses et des interprétations probables. Personne ne sait vraiment. ( pendant ce temps, l'hypothèse d'une fusion qui a mal tourné comme quelqu'un l'a suggéré dans les commentaires semble être un très bon candidat au vu d'autres incohérences d'indentation dans la même fonction).

Le seul fait objectif est qu'un goto dupliqué a conduit à quitter la fonction prématurément. En regardant le code, la seule autre instruction unique qui aurait pu provoquer le même effet aurait été un retour.

L'erreur se trouve dans la 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 conduit soit à une erreur de syntaxe lors de la compilation (et donc à une correction), soit à un goto inoffensif redondant. Soit dit en passant, GCC 6 pourrait 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 façons différentes qui auraient pu l'éviter:

Approche 1: clause if ou imbriquée ifs

Au lieu de tester de nombreuses conditions d'erreur séquentiellement, et à chaque envoi à une étiquette fail en cas de problème, on aurait pu opter pour l'exécution des opérations cryptographiques dans une instruction if- qui ferait l'affaire seulement s'il n'y avait pas de mauvaise condition préalable:

    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'erreurs

Cette approche est basée sur le fait que presque toutes les instructions ici appellent une fonction pour définir un code d'erreur err et n'exécutent le reste du code que si err était égal à 0 (c'est-à-dire une fonction exécutée sans erreur). Une belle 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'y a pas un seul goto: pas de 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 un ok && Oublié.

Cette construction est plus compacte. Elle est basée sur le fait qu'en C, la deuxième partie d'un logique et (&&) N'est évaluée que si la première partie est vraie. En fait, l'assembleur généré par un compilateur d'optimisation est presque équivalent au code d'origine 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 un contrôle de cohérence à la fin de la fonction qui pourrait au cours de la phase de test identifier les discordances entre l'indicateur ok et le code d'erreur.

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

Des erreurs telles qu'un ==0 Remplacé par inadvertance par un !=0 Ou des erreurs de connecteur logique seraient facilement 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 bug plus difficile à se produire.

64
Christophe

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

Dans la plupart des langages de programmation modernes, les fonctions sont définies de manière très spécifique 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 appelez-le et le point de sortie est l'endroit où vous retournez la valeur résultante et continuez l'exécution à l'instruction suivant l'appel de fonction d'origine.

Dans le cadre de cette fonction, vous devez être en mesure de faire tout ce que vous voulez, dans des limites raisonnables. Si mettre un ou deux goto dans la fonction le rend plus clair 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, afin que vous n'ayez plus à penser à son fonctionnement interne. Une fois qu'il est écrit, il vous suffit de l'utiliser.

Et oui, vous pouvez avoir plusieurs instructions de retour dans une fonction; il y a toujours toujours une place dans une fonction appropriée à partir de laquelle vous revenez (l'arrière de la fonction, essentiellement). Ce n'est pas du tout la même chose que de sauter d'une fonction avec un goto avant qu'elle n'ait une chance de revenir correctement.

enter image description here

Il ne s'agit donc pas vraiment d'utiliser des goto. Il s'agit d'éviter leurs abus. Tout le monde convient que vous pouvez créer un programme terriblement alambiqué à l'aide de gotos, mais vous pouvez également faire de même en abusant des fonctions (il est juste beaucoup plus facile d'abuser des gotos).

Pour ce que ça vaut, depuis que je suis diplômé des programmes BASIC de type numéro de ligne à la programmation structurée en utilisant Pascal et langages à accolades, je n'ai jamais eu à utiliser un goto. La seule fois où j'ai été tenté d'en utiliser une est de faire une sortie anticipée d'une boucle imbriquée (dans les langues qui ne prennent pas en charge la sortie anticipée à plusieurs niveaux des boucles), mais je peux généralement trouver une autre façon plus propre.

34
Robert Harvey

À quel genre de bogues les instructions "goto" conduisent-elles? Y a-t-il des exemples historiquement significatifs?

J'avais l'habitude d'utiliser des instructions goto lors de l'écriture de programmes BASIC en tant qu'enfant comme un moyen simple d'obtenir des boucles for et while (le Commodore 64 BASIC n'avait pas de boucles while, et j'étais trop immature pour apprendre la syntaxe appropriée et utilisation des boucles for). Mon code était souvent trivial, mais tout bogue 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'a pas besoin de goto.

Quand Edsger Dijkstra a déclaré "Goto considéré comme nuisible" en 1968, il n'a pas donné quelques exemples de bogues liés au goto, mais a déclaré que goto était inutile pour les langues de niveau supérieur) et qu'il faut éviter en faveur de ce que nous considérons aujourd'hui comme un flux de contrôle normal: les boucles et les conditions. Ses paroles:

L'utilisation débridée de aller à a pour conséquence immédiate qu'il devient terriblement difficile de trouver un ensemble significatif de coordonnées pour décrire la progression du processus.
[...]
L'instruction aller à en l'état est tout simplement trop primitive; c'est trop une invitation à gâcher son programme.

Il avait probablement des montagnes d'exemples de bogues à chaque fois qu'il déboguait du code avec goto dedans. Mais son article était une déclaration de position généralisée appuyée par la preuve que goto n'était pas nécessaire pour les langues de niveau supérieur. Le bogue généralisé est que vous ne pouvez peut-être pas analyser statiquement le code en question.

Le code est beaucoup plus difficile à analyser statiquement avec les instructions goto, surtout si vous remontez dans votre flux de contrôle (ce que j'avais l'habitude de faire) ou dans une section de code sans rapport. Le code peut et a été écrit de cette façon. Il a été fait pour optimiser fortement des ressources informatiques très rares et donc les architectures des machines.

Un exemple apocryphe

Il y avait un programme de blackjack qu'un mainteneur a trouvé assez optimisé avec élégance mais aussi impossible pour lui de "réparer" en raison de la nature du code. Il a été programmé en code machine qui dépend fortement des gotos - donc je pense que cette histoire est assez pertinente. C'est le meilleur exemple canonique auquel je puisse penser.

Un contre-exemple

Cependant, la source C de CPython (la plus courante et la référence Python) utilise à bon escient les instructions goto. Elles sont utilisées pour contourner le flux de contrôle non pertinent à l'intérieur des fonctions pour accéder à la fin des fonctions, ce qui rend les fonctions plus efficaces sans perte de lisibilité Cela respecte l'un des idéaux de programmation structurée - d'avoir un seul point de sortie pour les fonctions.

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

11
Aaron Hall

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

Lorsque le diligence était un art de transport actuel, leurs chauffeurs pouvaient sans aucun doute vous donner de bons conseils pratiques concernant les chevaux de diligences, comment se défendre contre bandits de grande route, et ainsi de suite. Heureusement, les automobilistes d'aujourd'hui peuvent également oublier la plupart de ces conseils.

Lorsque cartes perforées étaient de l'art de la programmation actuelle, leurs praticiens pouvaient également vous donner de bons conseils pratiques concernant l'organisation des cartes, la numérotation des déclarations, etc. Je ne suis pas sûr que ces conseils soient très pertinents aujourd'hui.

Êtes-vous assez vieux même pour connaître la phrase "to number statement" ? Vous n'avez pas besoin de le savoir, mais si vous ne le faites pas, vous ne connaissez pas le contexte historique dans lequel l'avertissement contre goto était principalement pertinent.

L'avertissement contre goto n'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 à émettre un goto très souvent. Quand vous y pensez, vous avez probablement une raison, alors allez-y.

Mais ne peut-on pas abuser de l'instruction goto?

Réponse: bien sûr, il peut être abusé, mais son abus est un très petit problème en génie logiciel par rapport à des erreurs beaucoup plus courantes telles que l'utilisation d'une variable où une constante servira, ou comme la programmation couper-coller (sinon connu sous le nom de négligence de refactoriser). Je doute que tu sois en grand danger. Sauf si vous utilisez longjmp ou transférez autrement le contrôle vers du code lointain, si vous pensez utiliser un goto ou si vous souhaitez simplement l'essayer pour le plaisir, allez-y. Ça ira.

Vous remarquerez peut-être le manque d'histoires d'horreur récentes dans lesquelles goto joue le méchant. La plupart ou la totalité de ces histoires semblent avoir 30 ou 40 ans. Vous vous tenez sur une base solide si vous considérez ces histoires comme principalement obsolètes.

11
thb

Pour ajouter une chose aux autres excellentes réponses, avec les instructions goto, 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 goto dans le code, il n'y a aucun moyen de dire quelles instructions exécutées entraînent cette exception provoquant l'état sans rechercher dans tout le programme. Il n'y a pas de pile d'appels et pas de flux visuel. Il est possible qu'il y ait une instruction à 1000 lignes de distance qui vous met dans un mauvais état a exécuté un goto à la ligne qui a déclenché une exception.

7
qfwfq

goto est plus difficile à raisonner pour les humains que les autres formes de contrôle de flux.

La programmation du code correct est difficile. Écrire des programmes corrects est difficile, déterminer si les programmes sont corrects est difficile, prouver que les programmes sont corrects est difficile.

Obtenir du code pour faire vaguement ce que vous voulez est facile par rapport à tout le reste de la programmation.

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

Il existe des styles de programmation où goto est la solution appropriée. Il existe même des styles de programmation où son jumeau maléfique, issu de, est la solution appropriée. Dans ces deux cas, une extrême prudence doit être apportée pour vous assurer que vous l'utilisez selon un schéma bien compris, et de nombreuses vérifications manuelles que vous ne faites pas quelque chose de difficile pour en garantir l'exactitude.

Par exemple, il existe une caractéristique de certaines langues appelées coroutines. Les coroutines sont des fils sans fil; état d'exécution sans thread pour l'exécuter. Vous pouvez leur demander d'exécuter, et ils peuvent exécuter une partie d'eux-mêmes, puis se suspendre, en remettant le contrôle de flux.

Les coroutines "superficielles" dans les langages sans support de coroutine (comme C++ pré-C++ 20 et C) sont possibles en utilisant un mélange de gotos et de gestion d'état manuelle. Les coroutines "profondes" peuvent être effectuées en utilisant les fonctions setjmp et longjmp de C.

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

Dans le cas de C++, ils sont jugés suffisamment utiles pour étendre le langage afin de les prendre en charge. Les gotos et la gestion manuelle de l'état sont cachés derrière une couche d'abstraction à coût nul, permettant aux programmeurs de les écrire sans la difficulté d'avoir à prouver leur désordre de goto, void**s, construction manuelle/destruction d'état, etc. est correct.

Le goto est caché derrière une abstraction de niveau supérieur, comme while ou for ou if ou switch. Ces abstractions de niveau supérieur sont plus faciles à prouver et à vérifier.

Si la langue était manquante certaines d'entre elles (comme certaines langues modernes manquent de coroutines), soit en chancelant avec un motif qui ne correspond pas au problème, soit en utilisant goto, devient votre alternative.

Faire en sorte qu'un ordinateur fasse vaguement ce que vous voulez qu'il fasse dans les cas courants est facile. L'écriture de code fiable et fiable est difficile. Goto aide le premier beaucoup plus que le second; d'où "goto considéré comme nuisible", car c'est un signe de code "fonctionnant" superficiellement avec des bogues profonds et difficiles à détecter. Avec un effort suffisant, vous pouvez toujours rendre le code avec "goto" fiable de manière fiable, il est donc incorrect de tenir la règle comme absolue; mais en règle générale, elle est bonne.

7
Yakk

Jetez un oeil à ce code, de http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html qui faisait en fait partie de une grande simulation du dilemme du prisonnier. Si vous avez vu de vieux codes FORTRAN ou BASIC, vous vous rendrez compte 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

Il y a beaucoup de problèmes ici qui vont bien au-delà de la déclaration GOTO ici; Je pense honnêtement que la déclaration 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 clair ce qui se passe. Même sans ajouter de commentaires ou utiliser de meilleurs noms de variables, le changer en une structure de bloc sans GOTO faciliterait la lecture et le suivi.

6
prosfilaes

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 goto en particulier. Si vous google, vous trouverez beaucoup d'erreurs causées par goto, ce n'est pas en soi une raison pour ne pas l'utiliser (des bugs se produisent toujours lorsque vous utilisez des fonctionnalités de langage parce que c'est intrinsèque dans la programmation), mais certains d'entre eux sont clairement très liés à goto utilisation.


Raisons d'utiliser/de ne pas utiliser goto :

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

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

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

  • Si vous devez quitter une fonction, il suffit de return.

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

  • CPython
  • libKTX
  • probablement quelques autres

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

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

Maintenant à cet endroit goto est utile car le langage est C:

  • nous sommes à l'intérieur d'une fonction
  • nous ne pouvons pas écrire une fonction de nettoyage (parce que nous entrons dans une autre portée, et rendre l'état de la fonction d'appelant accessible est plus lourd)

Ce cas d'utilisation est utile car en C nous n'avons pas de classes, donc la façon la plus simple de nettoyer est d'utiliser un 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 bogues cela peut conduire? N'importe quoi. Boucles infinies, mauvais ordre d'exécution, vis de pile ..

Un cas documenté est une vulnérabilité dans SSL qui a permis à Man in the middle d'attaques causées par une mauvaise utilisation de goto: - voici l'article

C'est une erreur de frappe, mais est restée inaperçue pendant un certain temps, si le code avait été structuré d'une autre manière, testé correctement, une telle erreur n'aurait pas pu être possible.

0
CoffeDeveloper

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

Au sein d'une même unité de code, le même principe s'applique. Si vous appliquez systématiquement des principes de programmation structurée ou de programmation orientée objet, vous ne:

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

Certains des symptômes les plus courants de ces erreurs de traitement incluent les fuites de mémoire, la thésaurisation de la mémoire, les débordements de pointeur, les plantages, les enregistrements de données incomplets, les exceptions d'ajout/modification/suppression d'enregistrement, les erreurs de page de mémoire, etc.

Les manifestations observables par l'utilisateur de ces problèmes comprennent le verrouillage de l'interface utilisateur, une 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 (à cause de la perte de contrôle des missiles à la perte de capacité de suivi et de contrôle dans les systèmes de contrôle de la circulation aérienne), de défaillances de temporisation, etc. Le catalogue est très étendu.

0
jaxter