Est-il jamais avantageux d'utiliser «goto» dans un langage qui prend en charge les boucles et les fonctions? Si oui, pourquoi?


201

J'ai depuis longtemps l'impression que cela gotone devrait jamais être utilisé si possible. En parcourant libavcodec (qui est écrit en C) l'autre jour, j'en ai remarqué plusieurs utilisations. Est-il jamais avantageux d'utiliser gotodans un langage qui prend en charge les boucles et les fonctions? Si oui, pourquoi?

Réponses:


242

Il y a quelques raisons d'utiliser l'énoncé "goto" que je connais (certains en ont déjà parlé):

Quitter proprement une fonction

Souvent, dans une fonction, vous pouvez allouer des ressources et devez quitter à plusieurs endroits. Les programmeurs peuvent simplifier leur code en plaçant le code de nettoyage des ressources à la fin de la fonction, et tous les "points de sortie" de la fonction se trouveront sur l'étiquette de nettoyage. De cette façon, vous n'avez pas à écrire de code de nettoyage à chaque "point de sortie" de la fonction.

Quitter les boucles imbriquées

Si vous êtes dans une boucle imbriquée et devez rompre toutes les boucles, un goto peut rendre cela beaucoup plus propre et plus simple que les instructions break et les if-checks.

Améliorations des performances de bas niveau

Ceci n'est valable que dans le code critique, mais les instructions goto s'exécutent très rapidement et peuvent vous donner un coup de pouce lors du déplacement dans une fonction. Il s'agit cependant d'une arme à double tranchant, car un compilateur ne peut généralement pas optimiser le code contenant des gotos.

Notez que dans tous ces exemples, les gotos sont limités à la portée d'une seule fonction.


15
La bonne façon de quitter les boucles imbriquées consiste à refactoriser la boucle interne dans une méthode distincte.
Jason

129
@Jason - Bah. C'est une charge de taureau. Remplacer gotopar returnest tout simplement stupide. Ce n'est pas "refactoriser" quoi que ce soit, c'est juste "renommer" pour que les personnes qui ont grandi dans un gotoenvironnement sans répression (c'est-à-dire nous tous) se sentent mieux à l'idée d'utiliser ce qui équivaut moralement à un goto. Je préfère de loin voir la boucle là où je l'utilise et en voir un peu goto, qui en soi n'est qu'un outil , que de voir quelqu'un ayant déplacé la boucle quelque part sans rapport juste pour éviter un goto.
Chris Lutz

18
Il y a des situations où les gotos sont importants: par exemple, un environnement C ++ sans exception. Dans la source Silverlight, nous avons des dizaines de milliers (ou plus) d'instructions goto pour une fonction sûre grâce à l'utilisation de macros - les codecs et bibliothèques de médias clés fonctionnent souvent avec des valeurs de retour et jamais d'exceptions, et il est difficile de combiner ces mécanismes de gestion des erreurs dans une seule manière performante.
Jeff Wilcox

79
Il convient de noter que tous break, continuedans le returnfond goto, sont simplement dans un bel emballage.
el.pescado

16
Je ne vois pas comment do{....}while(0)est censé être une meilleure idée que goto, sauf pour le fait que cela fonctionne en Java.
Liste Jeremy

906

Toute personne qui s'oppose goto, directement ou indirectement, à l'article GoTo Considered Harmful d' Edsger Dijkstra pour justifier sa position. Dommage que l'article de Dijkstra n'ait pratiquement rien à voir avec la façon dont les gotoinstructions sont utilisées de nos jours et que ce que dit l'article n'a donc pas ou peu d'application sur la scène de la programmation moderne. legoto mème -less confine maintenant sur une religion, jusque dans ses écritures dictées d'en haut, ses grands prêtres et l'évitement (ou pire) des hérétiques perçus.

Mettons le document de Dijkstra en contexte pour éclairer un peu le sujet.

Lorsque Dijkstra a écrit son article, les langues populaires de l'époque étaient des langues procédurales non structurées comme BASIC, FORTRAN (les anciens dialectes) et diverses langues d'assemblage. Il était assez courant que les personnes utilisant les langages de niveau supérieur sautent sur toute leur base de code dans des fils d'exécution tordus et déformés qui ont donné naissance au terme "code spaghetti". Vous pouvez le voir en passant au jeu Trek classique écrit par Mike Mayfield et en essayant de comprendre comment les choses fonctionnent. Prenez quelques instants pour regarder cela.

CECI est «l'utilisation effrénée de la déclaration« aller à »contre laquelle Dijkstra se penchait dans son journal en 1968. CECI est l'environnement dans lequel il vivait qui l'a amené à écrire ce journal. La possibilité de sauter où vous voulez dans votre code à tout moment que vous aimiez était ce qu'il critiquait et exigeait d'être arrêté. La comparaison avec les pouvoirs anémiques de gotoC ou d'autres langages plus modernes est tout simplement risible.

J'entends déjà les chants élevés des cultistes face à l'hérétique. "Mais," scandent-ils, "vous pouvez rendre le code très difficile à lire avecgoto en C." Oh oui? Vous pouvez également rendre le code très difficile à lire sans goto. Comme celui-ci:

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

Pas un gotoen vue, donc il doit être facile à lire, non? Ou que diriez-vous de celui-ci:

a[900];     b;c;d=1     ;e=1;f;     g;h;O;      main(k,
l)char*     *l;{g=      atoi(*      ++l);       for(k=
0;k*k<      g;b=k       ++>>1)      ;for(h=     0;h*h<=
g;++h);     --h;c=(     (h+=g>h     *(h+1))     -1)>>1;
while(d     <=g){       ++O;for     (f=0;f<     O&&d<=g
;++f)a[     b<<5|c]     =d++,b+=    e;for(      f=0;f<O
&&d<=g;     ++f)a[b     <<5|c]=     d++,c+=     e;e= -e
;}for(c     =0;c<h;     ++c){       for(b=0     ;b<k;++
b){if(b     <k/2)a[     b<<5|c]     ^=a[(k      -(b+1))
<<5|c]^=    a[b<<5      |c]^=a[     (k-(b+1     ))<<5|c]
;printf(    a[b<<5|c    ]?"%-4d"    :"    "     ,a[b<<5
|c]);}      putchar(    '\n');}}    /*Mike      Laman*/

Non gotonon plus. Il doit donc être lisible.

Quel est mon point avec ces exemples? Ce ne sont pas les fonctionnalités de langage qui rendent le code illisible et non maintenable. Ce n'est pas la syntaxe qui le fait. Ce sont les mauvais programmeurs qui causent cela. Et mauvais programmeurs, comme vous pouvez le voir dans cet article ci - dessus, peuvent faire une fonctionnalité de langage illisible et inutilisable. Comme les forboucles là-haut. (Vous pouvez les voir, non?)

Maintenant, pour être juste, certaines constructions de langage sont plus faciles à abuser que d'autres. Si vous êtes un programmeur C, cependant, je regarderais de plus près environ 50% des utilisations #definebien avant de partir en croisade goto!

Donc, pour ceux qui ont pris la peine de lire jusqu'ici, il y a plusieurs points clés à noter.

  1. Le document de Dijkstra sur les gotodéclarations a été écrit pour un environnement de programmation où gotoétait un beaucoup plus de dégâts potentiels que dans la plupart des langages modernes qui ne sont pas assembleurs.
  2. Jeter automatiquement toutes les utilisations de goto cause de cela est aussi rationnel que de dire "J'ai essayé de m'amuser une fois mais je ne l'aimais pas alors maintenant je suis contre".
  3. Il existe des utilisations légitimes du moderne (anémique) goto déclarations dans le code qui ne peuvent pas être remplacées de manière adéquate par d'autres constructions.
  4. Il y a, bien sûr, des utilisations illégitimes des mêmes déclarations.
  5. Il y a aussi des utilisations illégitimes des instructions de contrôle modernes comme l' godoabomination " " où une doboucle toujours fausse est rompue breakà la place de a goto. Ce sont souvent pires que l'utilisation judicieuse de goto.

42
@pocjoc: écriture d'une optimisation récursive de queue en C, par exemple. Cela se produit dans la mise en œuvre d'interprètes / runtimes de langage fonctionnel, mais est représentatif d'une catégorie intéressante de problèmes. La plupart des compilateurs écrits en C utilisent goto pour cette raison. C'est une utilisation de niche qui ne se produit pas dans la plupart des situations, bien sûr, mais dans les situations où il est appelé, cela a beaucoup de sens à utiliser.
zxq9

66
Pourquoi tout le monde parle-t-il du document de Dijkstra «Aller vers considéré comme nuisible» sans mentionner la réponse de Knuth, «Programmation structurée avec aller aux déclarations»?
Oblomov

22
@ Nietzche-jou "c'est un homme de paille assez flagrant [...]" Il utilise sarcastiquement une fausse dichotomie pour montrer pourquoi la stigmatisation est illogique. Un Strawman est une erreur logique où vous déformez intentionnellement la position de l'adversaire pour faire croire que sa position est facilement vaincue. Par exemple: "Les athées sont simplement athées parce qu'ils détestent Dieu". Une fausse dichotomie consiste à supposer que l'extrême opposé est vrai lorsqu'il existe au moins une possibilité supplémentaire (c'est-à-dire en noir et blanc). Par exemple, "Dieu déteste les homosexuels, donc il aime les hétéros.".
Braden Best

27
@JLRishe Vous lisez trop profondément quelque chose qui n'est même pas là. Ce n'est une véritable erreur logique que si la personne qui l'a utilisé croit honnêtement que cela a un sens logique. Mais il l'a dit sarcastiquement, indiquant clairement qu'il sait que c'est ridicule, et c'est ainsi qu'il le montre. Il "ne l'utilise pas pour justifier son point"; il l'utilise pour montrer pourquoi il est ridicule de dire que les gotos transforment automatiquement le code en spaghetti. C'est-à-dire que vous pouvez toujours écrire du code merdique sans gotos. Tel est son point. Et la fausse dichotomie sarcastique est sa méthode pour l'illustrer de manière humoristique.
Braden Best

32
-1 pour avoir expliqué très largement pourquoi la remarque de Dijkstra n'est pas applicable, tout en oubliant de montrer quels sont les avantages de goto ce qui est réellement (c'est la question posée)
Maarten Bodewes

154

Obéir aveuglément aux meilleures pratiques n'est pas une meilleure pratique. L'idée d'éviter les gotoinstructions comme forme principale de contrôle de flux est d'éviter de produire du code spaghetti illisible. S'ils sont utilisés avec parcimonie aux bons endroits, ils peuvent parfois être le moyen le plus simple et le plus clair d'exprimer une idée. Walter Bright, le créateur du compilateur Zortech C ++ et du langage de programmation D, les utilise fréquemment, mais judicieusement. Même avec les gotodéclarations, son code est toujours parfaitement lisible.

Bottom line: Éviter gotopour éviter gotoest inutile. Ce que vous voulez vraiment éviter, c'est produire du code illisible. Si votre gotocode -laden est lisible, il n'y a rien de mal à cela.


36

Étant donné gotoque le raisonnement sur le déroulement du programme est difficile 1 (alias «code spaghetti»), il goton'est généralement utilisé que pour compenser les fonctionnalités manquantes: l'utilisation de gotopeut effectivement être acceptable, mais uniquement si le langage n'offre pas de variante plus structurée à obtenir. le même objectif. Prenons l'exemple de Doubt:

La règle avec goto que nous utilisons est que goto est correct pour passer à un point de nettoyage de sortie unique dans une fonction.

C'est vrai - mais seulement si le langage ne permet pas la gestion structurée des exceptions avec du code de nettoyage (comme RAII ou finally), qui fait le même travail mieux (car il est spécialement conçu pour le faire), ou quand il y a une bonne raison de ne pas d'utiliser une gestion structurée des exceptions (mais vous n'aurez jamais ce cas, sauf à un niveau très bas).

Dans la plupart des autres langues, la seule utilisation acceptable de gotoest de quitter les boucles imbriquées. Et même là, il est presque toujours préférable de soulever la boucle extérieure dans une méthode propre et de l'utiliser à la returnplace.

Autre que cela, gotoest un signe que pas assez de réflexion est allée dans le morceau de code particulier.


1 Les langages modernes qui prennent gotoen charge implémentent certaines restrictions (par exemple, gotone peuvent pas entrer ou sortir des fonctions) mais le problème reste fondamentalement le même.

Soit dit en passant, il en va de même, bien entendu, pour les autres fonctionnalités linguistiques, notamment les exceptions. Et il existe généralement des règles strictes pour utiliser ces fonctionnalités uniquement lorsque cela est indiqué, comme la règle de ne pas utiliser d'exceptions pour contrôler le flux de programme non exceptionnel.


4
Juste curieux ici, mais qu'en est-il du cas de l'utilisation de gotos pour nettoyer le code. Par nettoyage, je veux dire, non seulement la désallocation de mémoire, mais aussi, disons la journalisation des erreurs. Je lisais un tas de messages, et apparemment, personne n'écrit de code qui imprime des journaux .. hmm?!
shiva

37
"Fondamentalement, en raison de la nature défectueuse de goto (et je pense que cela n'est pas controversé)" -1 et j'ai arrêté de lire là-bas.
o0 '.

14
L'admonestation contre goto vient de l'ère de la programmation structurée, où les premiers retours étaient également considérés comme mauvais. Déplacer des boucles imbriquées dans une fonction échange un "mal" contre un autre, et crée une fonction qui n'a aucune raison réelle d'exister (en dehors de "cela m'a permis d'éviter d'utiliser un goto!"). Il est vrai que goto est l'outil le plus puissant pour produire du code spaghetti s'il est utilisé sans contraintes, mais c'est une application parfaite pour cela; cela simplifie le code. Knuth a plaidé en faveur de cet usage et Dijkstra a déclaré: "Ne tombez pas dans le piège de croire que je suis terriblement dogmatique à propos de goto".
Boue

5
finally? Donc, utiliser des exceptions pour d'autres choses que la gestion des erreurs est une bonne chose, mais une gotomauvaise utilisation? Je pense que les exceptions portent bien leur nom.
Christian

3
@Mud: dans les cas que je rencontre (au quotidien), créer une fonction "qui n'a pas vraiment de raison d'exister" est la meilleure solution. Raison: il supprime les détails de la fonction de niveau supérieur, de sorte que le résultat a un flux de contrôle facile à lire. Je ne rencontre pas personnellement de situations où un goto produit le code le plus lisible. D'un autre côté, j'utilise plusieurs retours, dans des fonctions simples, où quelqu'un d'autre préférerait aller à un seul point de sortie. J'adorerais voir des contre-exemples où goto est plus lisible que la refactorisation en fonctions bien nommées.
ToolmakerSteve

35

Eh bien, il y a une chose qui est toujours pire que goto's; utilisation étrange d'autres opérateurs de flux de programme pour éviter un goto:

Exemples:

    // 1
    try{
      ...
      throw NoErrorException;
      ...
    } catch (const NoErrorException& noe){
      // This is the worst
    } 


    // 2
    do {
      ...break; 
      ...break;
    } while (false);


    // 3
    for(int i = 0;...) { 
      bool restartOuter = false;
      for (int j = 0;...) {
        if (...)
          restartOuter = true;
      if (restartOuter) {
        i = -1;
      }
    }

etc
etc

2
do{}while(false)Je pense que cela peut être considéré comme idiomatique. Vous n'êtes pas autorisé à être en désaccord: D
Thomas Eding

37
@trinithis: Si c'est "idiomatique", c'est uniquement à cause du culte anti-goto. Si vous y regardez de près, vous vous rendrez compte que c'est juste une façon de dire goto after_do_block;sans vraiment le dire. Sinon ... une "boucle" qui s'exécute exactement une fois? J'appellerais cela un abus des structures de contrôle.
cHao

5
@ThomasEding Eding Il y a une exception à votre propos. Si vous avez déjà fait de la programmation C / C ++ et que vous avez dû utiliser #define, vous savez, cela fait {} tandis que (0) est l'une des normes pour encapsuler plusieurs lignes de code. Par exemple: #define do {memcpy (a, b, 1); quelque chose ++;} while (0) est plus sûr et meilleur que #define memcpy (a, b, 1); quelque chose ++
Ignas2526

10
@ Ignas2526 Vous venez de montrer très joliment à quel point il #definey en a beaucoup, plusieurs fois bien pire que de l'utiliser de gototemps en temps: D
Luaan

3
+1 pour une bonne liste de façons dont les gens essaient d'éviter les gotos, à l'extrême; J'ai vu des mentions de telles techniques ailleurs, mais c'est une excellente liste succincte. Réfléchissant pourquoi, au cours des 15 dernières années, mon équipe n'a jamais eu besoin de ces techniques, ni utilisé de gotos. Même dans une application client / serveur avec des centaines de milliers de lignes de code, par plusieurs programmeurs. C #, java, PHP, python, javascript. Code client, serveur et application. Ne pas se vanter, ni faire du prosélytisme pour une position, juste vraiment curieux de savoir pourquoi certaines personnes rencontrent des situations qui demandent des gotos comme solution la plus claire, et d'autres pas ...
ToolmakerSteve

28

En C # commutateur déclaration doest permettent pas fall-through . Donc, goto est utilisé pour transférer le contrôle vers une étiquette de boîtier de commutation spécifique ou l' étiquette par défaut .

Par exemple:

switch(value)
{
  case 0:
    Console.Writeln("In case 0");
    goto case 1;
  case 1:
    Console.Writeln("In case 1");
    goto case 2;
  case 2:
    Console.Writeln("In case 2");
    goto default;
  default:
    Console.Writeln("In default");
    break;
}

Edit: Il y a une exception à la règle "pas d'interruption". La reprise est autorisée si une instruction case n'a pas de code.



9
Seulement si le boîtier n'a pas de corps de code. S'il contient du code, vous devez utiliser le mot-clé goto.
Matthew Whited,

26
Cette réponse est tellement drôle - C # a supprimé les retombées parce que beaucoup le voient comme nocif, et cet exemple utilise goto (également considéré comme nocif par beaucoup) pour raviver le comportement d'origine, soi-disant nocif, MAIS le résultat global est en fait moins nocif ( parce que le code indique clairement que la solution est intentionnelle!).
thomasrutter

9
Ce n'est pas parce qu'un mot-clé est écrit avec les lettres GOTO que cela devient un goto. Ce cas présenté n'est pas un goto. Il s'agit d'une construction d'instruction switch pour fallthrough. Là encore, je ne connais pas très bien C #, donc je peux me tromper.
Thomas Eding,

1
Eh bien, dans ce cas, c'est un peu plus que la chute (parce que vous pouvez dire goto case 5:quand vous êtes dans le cas 1). Il semble que la réponse de Konrad Rudolph soit correcte ici: gotocompense une fonctionnalité manquante (et est moins claire que la fonctionnalité réelle ne le serait). Si ce que nous voulons vraiment, c'est une solution de traversée, le meilleur défaut serait peut-être la non-transition, mais quelque chose comme continuela demander explicitement.
David Stone

14

#ifdef TONGUE_IN_CHEEK

Perl a un gotoqui vous permet d'implémenter les appels de queue du pauvre. :-P

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

D' accord, donc cela n'a rien à voir avec C de goto. Plus sérieusement, je suis d'accord avec les autres commentaires sur l'utilisation gotopour les nettoyages, ou pour implémenter l'appareil de Duff , ou similaire. Il s'agit d'utiliser, pas d'abuser.

(Le même commentaire peut s'appliquer aux longjmpexceptions call/ccet similaires --- elles ont des utilisations légitimes, mais peuvent facilement être utilisées à mauvais escient. Par exemple, lever une exception uniquement pour échapper à une structure de contrôle profondément imbriquée, dans des circonstances totalement non exceptionnelles .)


Je pense que c'est la seule raison d'utiliser goto en Perl.
Brad Gilbert

12

J'ai écrit plus de quelques lignes de langage d'assemblage au fil des ans. En fin de compte, chaque langage de haut niveau se compile en gotos. D'accord, appelez-les "branches" ou "sauts" ou quoi que ce soit d'autre, mais ce sont des gotos. Quelqu'un peut-il écrire un assembleur goto-less?

Maintenant, bien sûr, vous pouvez signaler à un programmeur Fortran, C ou BASIC que lancer une émeute avec des gotos est une recette de spaghetti bolognaise. La réponse n'est cependant pas de les éviter, mais de les utiliser avec précaution.

Un couteau peut être utilisé pour préparer de la nourriture, libérer quelqu'un ou tuer quelqu'un. Faisons-nous sans couteaux par peur de ces derniers? De même le goto: utilisé avec négligence, il gêne, utilisé avec précaution, il aide.


1
Peut-être voulez-vous lire pourquoi je pense que c'est fondamentalement faux à stackoverflow.com/questions/46586/…
Konrad Rudolph

15
Quiconque avance le "tout compile en JMP de toute façon!" L'argument ne comprend pas fondamentalement le point derrière la programmation dans un langage de niveau supérieur.
Nietzche-jou

7
Tout ce dont vous avez vraiment besoin est de soustraire et de dériver. Tout le reste est pour la commodité ou la performance.
David Stone

8

Jetez un œil à Quand utiliser Goto lors de la programmation en C :

Bien que l'utilisation de goto soit presque toujours une mauvaise pratique de programmation (vous pouvez sûrement trouver une meilleure façon de faire XYZ), il y a des moments où ce n'est vraiment pas un mauvais choix. Certains pourraient même affirmer que lorsque c'est utile, c'est le meilleur choix.

La plupart de ce que j'ai à dire sur goto ne s'applique vraiment qu'à C. Si vous utilisez C ++, il n'y a aucune raison valable d'utiliser goto à la place des exceptions. En C, cependant, vous n'avez pas la puissance d'un mécanisme de gestion des exceptions, donc si vous voulez séparer la gestion des erreurs du reste de la logique de votre programme et éviter de réécrire le code de nettoyage plusieurs fois dans votre code, alors goto peut être un bon choix.

Qu'est ce que je veux dire? Vous pourriez avoir un code qui ressemble à ceci:

int big_function()
{
    /* do some work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* do some more work */
    if([error])
    {
        /* clean up*/
        return [error];
    }
    /* clean up*/
    return [success];
}

C'est très bien jusqu'à ce que vous réalisiez que vous devez changer votre code de nettoyage. Ensuite, vous devez passer par et apporter 4 modifications. Maintenant, vous pourriez décider que vous pouvez simplement encapsuler tout le nettoyage dans une seule fonction; ce n'est pas une mauvaise idée. Mais cela signifie que vous devrez faire attention aux pointeurs - si vous prévoyez de libérer un pointeur dans votre fonction de nettoyage, il n'y a aucun moyen de le définir pour pointer sur NULL à moins que vous ne passiez un pointeur sur un pointeur. Dans de nombreux cas, vous n'utiliserez plus ce pointeur de toute façon, ce qui peut ne pas être une préoccupation majeure. D'un autre côté, si vous ajoutez un nouveau pointeur, un nouveau descripteur de fichier ou une autre chose qui nécessite un nettoyage, vous devrez à nouveau modifier votre fonction de nettoyage; puis vous devrez modifier les arguments de cette fonction.

En utilisant goto, il sera

int big_function()
{
    int ret_val = [success];
    /* do some work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
    /* do some more work */
    if([error])
    {
        ret_val = [error];
        goto end;
    }
end:
    /* clean up*/
    return ret_val;
}

L'avantage ici est que votre code suivant la fin a accès à tout ce dont il aura besoin pour effectuer le nettoyage, et vous avez réussi à réduire considérablement le nombre de points de changement. Un autre avantage est que vous êtes passé de plusieurs points de sortie pour votre fonction à un seul; il n'y a aucune chance que vous reveniez accidentellement de la fonction sans nettoyer.

De plus, puisque goto n'est utilisé que pour sauter à un seul point, ce n'est pas comme si vous créez une masse de code spaghetti qui va et vient dans le but de simuler des appels de fonction. Au contraire, goto aide en fait à écrire du code plus structuré.


En un mot, gotodevrait toujours être utilisé avec parcimonie et en dernier recours - mais il y a un temps et un lieu pour cela. La question ne devrait pas être "devez-vous l'utiliser" mais "est-ce le meilleur choix" pour l'utiliser.


7

Une des raisons pour lesquelles goto est mauvais, outre le style de codage, c'est que vous pouvez l'utiliser pour créer des boucles superposées mais non imbriquées :

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

Cela créerait une structure de contrôle de flux bizarre, mais peut-être légale, où une séquence comme (a, b, c, b, a, b, a, b, ...) est possible, ce qui rend les pirates du compilateur mécontents. Apparemment, il existe un certain nombre de astuces d'optimisation intelligentes qui reposent sur ce type de structure qui ne se produit pas. (Je devrais vérifier ma copie du livre du dragon ...) Le résultat de ceci pourrait (en utilisant certains compilateurs) être que d'autres optimisations ne sont pas faites pour le code qui contient gotos.

Cela peut être utile si vous le savez , "oh, au fait", arrive à persuader le compilateur d'émettre du code plus rapidement. Personnellement, je préfère essayer d'expliquer au compilateur ce qui est probable et ce qui ne l'est pas avant d'utiliser une astuce comme goto, mais sans doute, je pourrais aussi essayer gotoavant de pirater l'assembleur.


3
Eh bien ... me ramène à l'époque de la programmation de FORTRAN dans une société d'information financière. En 2007.
Marcin

5
Toute structure de langage peut être utilisée abusivement de manière à la rendre illisible ou peu performante.
JUSTE MON AVIS correct

@JUST: Il ne s'agit pas de lisibilité ou de performances médiocres, mais d'hypothèses et de garanties sur le graphique de flux de contrôle. Tout abus de goto serait pour des performances accrues (ou lisibilité).
Anders Eurenius

3
Je dirais qu'une des raisons gotoest utile, c'est qu'elle vous permet de construire des boucles comme celle-ci, ce qui nécessiterait un tas de contorsions logiques sinon. Je dirais en outre que si l'optimiseur ne sait pas comment réécrire cela, alors c'est bien . Une boucle comme celle-ci ne devrait pas être effectuée pour des performances ou une lisibilité, mais parce que c'est exactement l'ordre dans lequel les choses doivent se produire. Dans ce cas, je ne voudrais pas particulièrement que l'optimiseur tourne avec.
cHao

1
... une traduction simple de l'algorithme requis en code basé sur GOTO peut être beaucoup plus facile à valider qu'un gâchis de variables de drapeau et de constructions en boucle abusées.
supercat

7

Je trouve drôle que certaines personnes iront jusqu'à donner une liste de cas où goto est acceptable, en disant que toutes les autres utilisations sont inacceptables. Pensez-vous vraiment que vous connaissez tous les cas où goto est le meilleur choix pour exprimer un algorithme?

Pour illustrer, je vais vous donner un exemple que personne ici n'a encore montré:

Aujourd'hui, j'écrivais du code pour insérer un élément dans une table de hachage. La table de hachage est un cache des calculs précédents qui peuvent être écrasés à volonté (affectant les performances mais pas l'exactitude).

Chaque compartiment de la table de hachage a 4 emplacements, et j'ai un tas de critères pour décider quel élément remplacer par un compartiment plein. À l'heure actuelle, cela signifie effectuer jusqu'à trois passages dans un seau, comme ceci:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    goto add;

// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
    goto add;

// Additional passes go here...

add:
// element is written to the hash table here

Maintenant, si je n'utilisais pas goto, à quoi ressemblerait ce code?

Quelque chose comme ça:

// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
  if (slot_p[add_index].hash_key == hash_key)
    break;

if (add_index >= ELEMENTS_PER_BUCKET) {
  // Otherwise, find first empty element
  for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
    if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
      break;
  if (add_index >= ELEMENTS_PER_BUCKET)
   // Additional passes go here (nested further)...
}

// element is written to the hash table here

Il semblerait de pire en pire si plus de passes sont ajoutées, tandis que la version avec goto conserve le même niveau d'indentation à tout moment et évite l'utilisation d'instructions if parasites dont le résultat est impliqué par l'exécution de la boucle précédente.

Il y a donc un autre cas où goto rend le code plus propre et plus facile à écrire et à comprendre ... Je suis sûr qu'il y en a beaucoup plus, alors ne faites pas semblant de connaître tous les cas où goto est utile, en dissipant les bons que vous ne pourriez pas n'y pense pas.


1
Dans l'exemple que vous avez donné, je préférerais refactoriser cela de manière assez significative. En général, j'essaie d'éviter les commentaires unilatéraux qui disent ce que fait le prochain morceau de code. Au lieu de cela, je décompose cela en sa propre fonction nommée de la même manière que le commentaire. Si vous deviez effectuer une telle transformation, alors cette fonction donnerait un aperçu de haut niveau de ce que fait la fonction, et chacune des nouvelles fonctions indiquerait comment vous effectuez chaque étape. Je pense qu'il est beaucoup plus important que toute opposition d' gotoavoir que chaque fonction soit au même niveau d'abstraction. Cela évite gotoest un bonus.
David Stone

2
Vous n'avez pas vraiment expliqué comment l'ajout de fonctions supprime le goto, les multiples niveaux d'indentation et les fausses instructions if ...
Ricardo

Cela ressemblerait à quelque chose comme ça, en utilisant la notation de conteneur standard: container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();je trouve ce type de programmation beaucoup plus facile à lire. Dans ce cas, chaque fonction peut être écrite comme une fonction pure, ce qui est beaucoup plus facile à raisonner. La fonction principale explique maintenant ce que fait le code uniquement par le nom des fonctions, puis si vous le souhaitez, vous pouvez consulter leurs définitions pour savoir comment il le fait.
David Stone

3
Le fait que quiconque doive donner des exemples de la façon dont certains algorithmes devraient naturellement utiliser un goto est une triste réflexion sur le peu de réflexion algorithmique qui se passe aujourd'hui !! Bien sûr, l'exemple de @ Ricardo est (l'un des nombreux) exemples parfaits où goto est élégant et évident.
Fattie

6

La règle avec goto que nous utilisons est que goto est correct pour passer à un point de nettoyage de sortie unique dans une fonction. Dans les fonctions vraiment complexes, nous assouplissons cette règle pour permettre aux autres sauts en avant. Dans les deux cas, nous évitons les instructions si profondément imbriquées qui se produisent souvent lors de la vérification du code d'erreur, ce qui améliore la lisibilité et la maintenance.


1
Je pourrais voir quelque chose comme ça être utile dans un langage comme C. Cependant, quand vous avez la puissance des constructeurs / destructeurs C ++, ce n'est généralement pas si utile.
David Stone

1
"Dans les fonctions vraiment complexes, nous assouplissons cette règle pour permettre à d'autres sauts en avant" Sans exemple, cela ressemble à, si vous rendez des fonctions complexes encore plus complexes en utilisant des sauts. La "meilleure" approche ne serait-elle pas de refactoriser et de diviser ces fonctions complexes?
MikeMB

1
C'était le dernier but pour lequel j'ai utilisé goto. Je ne manque pas goto en Java, car il a enfin un essai qui peut faire le même travail.
Patricia Shanahan

5

La discussion la plus réfléchie et approfondie des instructions goto, de leurs utilisations légitimes et des constructions alternatives qui peuvent être utilisées à la place des "instructions goto vertueuses" mais peuvent être utilisées aussi facilement que les instructions goto, est l'article de Donald Knuth " Programmation structurée avec des instructions goto " , dans les Computing Surveys de décembre 1974 (volume 6, n ° 4. pp. 261 - 301).

Il n'est pas surprenant que certains aspects de ce document de 39 ans soient datés: des augmentations de l'ordre de grandeur de la puissance de traitement rendent certaines des améliorations de performances de Knuth imperceptibles pour des problèmes de taille moyenne, et de nouvelles constructions de langage de programmation ont été inventées depuis. (Par exemple, les blocs try-catch subsument la construction de Zahn, bien qu'ils soient rarement utilisés de cette façon.) Mais Knuth couvre tous les côtés de l'argument, et devrait être lu avant que quiconque ne ressasse le problème.


3

Dans un module Perl, vous souhaitez parfois créer des sous-programmes ou des fermetures à la volée. Le fait est qu'une fois que vous avez créé le sous-programme, comment y accéder. Vous pouvez simplement l'appeler, mais si le sous-programme l'utilise, caller()il ne sera pas aussi utile qu'il pourrait l'être. C'est là que la goto &subroutinevariation peut être utile.

Voici un petit exemple:

sub AUTOLOAD{
  my($self) = @_;
  my $name = $AUTOLOAD;
  $name =~ s/.*:://;

  *{$name} = my($sub) = sub{
    # the body of the closure
  }

  goto $sub;

  # nothing after the goto will ever be executed.
}

Vous pouvez également utiliser cette forme de gotopour fournir une forme rudimentaire d'optimisation des appels de queue.

sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;

  $tally *= $n--;
  @_ = ($n,$tally);
  goto &factorial;
}

(Dans Perl 5 version 16, ce serait mieux écrit comme goto __SUB__;)

Il y a un module qui importera un tailmodificateur et un qui importera recursi vous n'aimez pas utiliser cette forme de goto.

use Sub::Call::Tail;
sub AUTOLOAD {
  ...
  tail &$sub( @_ );
}

use Sub::Call::Recur;
sub factorial($){
  my($n,$tally) = (@_,1);

  return $tally if $n <= 1;
  recur( $n-1, $tally * $n );
}

La plupart des autres raisons d'utiliser gotosont mieux faites avec d'autres mots clés.

Comme redoun peu de code:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

Ou en allant à lastun peu de code à partir de plusieurs endroits:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

2

Si oui, pourquoi?

C n'a pas de rupture à plusieurs niveaux / étiquetée, et tous les flux de contrôle ne peuvent pas être facilement modélisés avec les primitives d'itération et de décision de C. les gotos contribuent grandement à corriger ces défauts.

Parfois, il est plus clair d'utiliser une variable d'indicateur d'une sorte pour effectuer une sorte de coupure pseudo-multi-niveaux, mais elle n'est pas toujours supérieure au goto (au moins un goto permet de déterminer facilement où va le contrôle, contrairement à une variable d'indicateur ), et parfois vous ne voulez tout simplement pas payer le prix de performance des drapeaux / autres contorsions pour éviter le goto.

libavcodec est un morceau de code sensible aux performances. L'expression directe du flux de contrôle est probablement une priorité, car elle aura tendance à mieux fonctionner.


2

Tout aussi bien personne n'a jamais mis en œuvre la déclaration "COME FROM" ....


8
Ou en C ++, C #, Java, JS, Python, Ruby .... etc etc etc. Seulement, ils l'appellent "exceptions".
cHao

2

Je trouve le do {} tout en (faux) usage tout à fait révoltant. Il est concevable pourrait me convaincre qu'il est nécessaire dans certains cas étranges, mais jamais qu'il s'agit d'un code propre et sensible.

Si vous devez faire une telle boucle, pourquoi ne pas rendre explicite la dépendance à la variable indicateur?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

N'est-ce /*empty*/pas stepfailed = 1? En tout cas, comment est-ce mieux qu'un do{}while(0)? Dans les deux, vous devez en breaksortir (ou dans le vôtre stepfailed = 1; continue;). Cela me semble inutile.
Thomas Eding

2

1) L'utilisation la plus courante de goto que je connaisse est l'émulation de la gestion des exceptions dans les langages qui ne l'offrent pas, à savoir en C. (Le code donné par Nuclear ci-dessus est juste cela.) Regardez le code source Linux et vous ' Je verrai un bazillion de gotos utilisés de cette façon; il y avait environ 100 000 gotos dans le code Linux selon une enquête rapide menée en 2013: http://blog.regehr.org/archives/894 . L'utilisation de Goto est même mentionnée dans le guide de style de codage Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Tout comme la programmation orientée objet est émulée à l'aide de structures peuplées de pointeurs de fonction, goto a sa place dans la programmation C. Alors, qui a raison: Dijkstra ou Linus (et tous les codeurs du noyau Linux)? C'est de la théorie contre la pratique.

Il y a cependant le piège habituel de ne pas avoir de support au niveau du compilateur et de vérifier les constructions / modèles courants: il est plus facile de les mal utiliser et d'introduire des bogues sans vérifications au moment de la compilation. Windows et Visual C ++ mais en mode C offrent une gestion des exceptions via SEH / VEH pour cette raison: les exceptions sont utiles même en dehors des langages OOP, c'est-à-dire dans un langage procédural. Mais le compilateur ne peut pas toujours sauvegarder votre bacon, même s'il offre un support syntaxique pour les exceptions dans le langage. Prenons comme exemple de ce dernier cas le fameux bogue Apple SSL "goto fail", qui vient de dupliquer un goto avec des conséquences désastreuses ( https://www.imperialviolet.org/2014/02/22/applebug.html ):

if (something())
  goto fail;
  goto fail; // copypasta bug
printf("Never reached\n");
fail:
  // control jumps here

Vous pouvez avoir exactement le même bogue en utilisant des exceptions prises en charge par le compilateur, par exemple en C ++:

struct Fail {};

try {
  if (something())
    throw Fail();
    throw Fail(); // copypasta bug
  printf("Never reached\n");
}
catch (Fail&) {
  // control jumps here
}

Mais les deux variantes du bogue peuvent être évitées si le compilateur analyse et vous avertit du code inaccessible. Par exemple, la compilation avec Visual C ++ au niveau d'avertissement / W4 trouve le bogue dans les deux cas. Java, par exemple, interdit le code inaccessible (où il peut le trouver!) Pour une assez bonne raison: il s'agit probablement d'un bogue dans le code moyen de Joe. Tant que la construction goto ne permet pas aux cibles que le compilateur ne peut pas facilement comprendre, comme les gotos aux adresses calculées (**), il n'est pas plus difficile pour le compilateur de trouver du code inaccessible dans une fonction avec gotos que d'utiliser Dijkstra - code approuvé.

(**) Note de bas de page: Les gotos vers les numéros de ligne calculés sont possibles dans certaines versions de Basic, par exemple GOTO 10 * x où x est une variable. De façon assez confuse, dans Fortran, "goto calculé" fait référence à une construction qui est équivalente à une instruction switch en C. La norme C n'autorise pas les gotos calculés dans le langage, mais seulement les gotos aux étiquettes déclarées statiquement / syntaxiquement. GNU C a cependant une extension pour obtenir l'adresse d'une étiquette (l'opérateur unaire, préfixe &&) et permet également d'accéder à une variable de type void *. Voir https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html pour en savoir plus sur ce sous-sujet obscur. Le reste de ce post ne concerne pas cette obscure fonctionnalité GNU C.

Les gotos C standard (c'est-à-dire non calculés) ne sont généralement pas la raison pour laquelle le code inaccessible ne peut pas être trouvé au moment de la compilation. La raison habituelle est un code logique comme le suivant. Donné

int computation1() {
  return 1;
}

int computation2() {
  return computation1();
}

Il est tout aussi difficile pour un compilateur de trouver du code inaccessible dans l'une des 3 constructions suivantes:

void tough1() {
  if (computation1() != computation2())
    printf("Unreachable\n");
}

void tough2() {
  if (computation1() == computation2())
    goto out;
  printf("Unreachable\n");
out:;
}

struct Out{};

void tough3() {
  try {
    if (computation1() == computation2())
      throw Out();
    printf("Unreachable\n");
  }
  catch (Out&) {
  }
}

(Excusez mon style de codage lié à l'accolade, mais j'ai essayé de garder les exemples aussi compacts que possible.)

Visual C ++ / W4 (même avec / Ox) ne parvient pas à trouver de code inaccessible dans aucun d'entre eux, et comme vous le savez probablement, le problème de la recherche de code inaccessible est en général indécidable. (Si vous ne me croyez pas à ce sujet: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )

Comme problème connexe, le goto C peut être utilisé pour émuler des exceptions uniquement à l'intérieur du corps d'une fonction. La bibliothèque C standard offre une paire de fonctions setjmp () et longjmp () pour émuler des sorties / exceptions non locales, mais celles-ci présentent de sérieux inconvénients par rapport à ce que d'autres langues offrent. L'article Wikipedia http://en.wikipedia.org/wiki/Setjmp.h explique assez bien ce dernier problème. Cette paire de fonctions fonctionne également sous Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), mais presque personne ne les utilise là-bas car SEH / VEH est supérieur. Même sous Unix, je pense que setjmp et longjmp sont très rarement utilisés.

2) Je pense que la deuxième utilisation la plus courante de goto en C consiste à implémenter une interruption à plusieurs niveaux ou une poursuite à plusieurs niveaux, ce qui est également un cas d'utilisation assez peu controversé. Rappelez-vous que Java n'autorise pas le goto label, mais autorise break label ou continue label. Selon http://www.oracle.com/technetwork/java/simple-142616.html , il s'agit en fait du cas d'utilisation le plus courant des gotos en C (90% disent-ils), mais selon mon expérience subjective, le code système tend à d'utiliser gotos pour la gestion des erreurs plus souvent. Peut-être dans le code scientifique ou lorsque le système d'exploitation offre une gestion des exceptions (Windows), alors les sorties à plusieurs niveaux sont le cas d'utilisation dominant. Ils ne donnent pas vraiment de détails sur le contexte de leur enquête.

Modifié pour ajouter: il s'avère que ces deux modèles d'utilisation se trouvent dans le livre C de Kernighan et Ritchie, vers la page 60 (selon l'édition). Une autre chose à noter est que les deux cas d'utilisation impliquent uniquement des gotos avancés. Et il s'avère que l'édition MISRA C 2012 (contrairement à l'édition 2004) autorise désormais les gotos, tant qu'ils ne sont que des versions avancées.


Droite. "L'éléphant dans la salle" est que le noyau Linux, pour l'amour du ciel, comme un exemple d'une base de code critique pour le monde - est chargé avec goto. Bien sûr que oui. Évidemment. L '"anti-goto-meme" n'est qu'une curiosité d'il y a des décennies. Bien sûr, il y a un certain nombre de choses dans la programmation (notamment "statique", en fait "globales" et par exemple "elseif") qui peuvent être abusées par des non-professionnels. Donc, si vous êtes un cousin enfant, vous apprenez le programme 2, vous leur dites "oh n'utilisez pas les globaux" et "n'utilisez jamais elseif".
Fattie

Le bug goto fail n'a rien à voir avec goto. Le problème est provoqué par l'instruction if sans accolades par la suite. À peu près n'importe quelle copie de déclaration collée deux fois, cela aurait causé un problème. Je considère que ce type de nu sans bretelles est bien plus dangereux que goto.
muusbolla

2

Certains disent qu'il n'y a pas de raison pour goto en C ++. Certains disent que dans 99% des cas, il existe de meilleures alternatives. Ce n'est pas du raisonnement, juste des impressions irrationnelles. Voici un exemple solide où goto mène à un joli code, quelque chose comme une boucle do-while améliorée:

int i;

PROMPT_INSERT_NUMBER:
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    goto PROMPT_INSERT_NUMBER;          
  }

std::cout << "your number is " << i;

Comparez-le au code gratuit:

int i;

bool loop;
do {
  loop = false;
  std::cout << "insert number: ";
  std::cin >> i;
  if(std::cin.fail()) {
    std::cin.clear();
    std::cin.ignore(1000,'\n');
    loop = true;          
  }
} while(loop);

std::cout << "your number is " << i;

Je vois ces différences:

  • un {}bloc imbriqué est nécessaire (bien qu'il do {...} whilesemble plus familier)
  • une loopvariable supplémentaire est nécessaire, utilisée à quatre endroits
  • il faut plus de temps pour lire et comprendre le travail avec le loop
  • le loopne contient aucune donnée, il contrôle simplement le flux de l'exécution, ce qui est moins compréhensible qu'une simple étiquette

Il y a un autre exemple

void sort(int* array, int length) {
SORT:
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    goto SORT; // it is very easy to understand this code, right?
  }
}

Maintenant, débarrassons-nous du goto "diabolique":

void sort(int* array, int length) {
  bool seemslegit;
  do {
    seemslegit = true;
    for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
      swap(data[i], data[i+1]);
      seemslegit = false;
    }
  } while(!seemslegit);
}

Vous voyez que c'est le même type d'utilisation de goto, c'est un modèle bien structuré et ce n'est pas un goto en avant autant promu que la seule façon recommandée. Vous voulez sûrement éviter un code "intelligent" comme celui-ci:

void sort(int* array, int length) {
  for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
    swap(data[i], data[i+1]);
    i = -1; // it works, but WTF on the first glance
  }
}

Le fait est que goto peut être facilement utilisé à mauvais escient, mais goto lui-même n'est pas à blâmer. Notez que l'étiquette a une portée de fonction en C ++, donc elle ne pollue pas la portée globale comme dans un assemblage pur, dans lequel les boucles qui se chevauchent ont leur place et sont très courantes - comme dans le code suivant pour 8051, où l'affichage à 7 segments est connecté à P1. Le programme boucle le segment de la foudre autour de:

; P1 states loops
; 11111110 <-
; 11111101  |
; 11111011  |
; 11110111  |
; 11101111  |
; 11011111  |
; |_________|

init_roll_state:
    MOV P1,#11111110b
    ACALL delay
next_roll_state:
    MOV A,P1
    RL A
    MOV P1,A
    ACALL delay
    JNB P1.5, init_roll_state
    SJMP next_roll_state

Il y a un autre avantage: goto peut servir de boucles nommées, de conditions et d'autres flux:

if(valid) {
  do { // while(loop)

// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket

  } while(loop);
} // if(valid)

Ou vous pouvez utiliser un goto équivalent avec indentation, vous n'avez donc pas besoin de commentaire si vous choisissez judicieusement le nom de l'étiquette:

if(!valid) goto NOTVALID;
  LOOPBACK:

// more than one page of code here

  if(loop) goto LOOPBACK;
NOTVALID:;

1

En Perl, utilisation d'une étiquette pour "goto" à partir d'une boucle - en utilisant une "dernière" instruction, qui est similaire à break.

Cela permet un meilleur contrôle sur les boucles imbriquées.

L' étiquette goto traditionnelle est également prise en charge, mais je ne suis pas sûr qu'il existe trop d'exemples où c'est la seule façon d'atteindre ce que vous voulez - les sous-programmes et les boucles devraient suffire dans la plupart des cas.


Je pense que la seule forme de goto que vous utiliseriez en Perl est goto &subroutine. Ce qui démarre le sous-programme avec le @_ courant, tout en remplaçant le sous-programme actuel dans la pile.
Brad Gilbert

1

Le problème avec «goto» et l'argument le plus important du mouvement de «programmation sans goto» est que si vous l'utilisez trop fréquemment, votre code, bien qu'il puisse se comporter correctement, devient illisible, non maintenable, non révisable, etc. Dans 99,99% des le cas «goto» mène au code spaghetti. Personnellement, je ne vois aucune bonne raison pour laquelle j'utiliserais «goto».


11
Dire "je ne peux penser à aucune raison" dans votre argument est, formellement, en.wikipedia.org/wiki/Argument_from_ignorance , bien que je préfère la terminologie "preuve par manque d'imagination".
JUSTE MON AVIS correct

2
@JUST MON AVIS correct: C'est une erreur logique seulement si elle est employée post-hoc. Du point de vue d'un concepteur de langage, il peut être un argument valable pour peser le coût de l'inclusion d'une fonctionnalité ( goto). L'utilisation de @ cschol est similaire: bien qu'il ne conçoive peut-être pas de langage en ce moment, il évalue essentiellement les efforts du concepteur.
Konrad Rudolph

1
@KonradRudolph: À mon humble avis, le fait d'avoir un langage autorisé gotosauf dans des contextes où il entraînerait l'existence de variables est susceptible d'être moins cher que d'essayer de prendre en charge tous les types de structure de contrôle dont quelqu'un pourrait avoir besoin. Écrire du code avec gotopeut ne pas être aussi agréable que d'utiliser une autre structure, mais pouvoir écrire un tel code avec gotoaidera à éviter les "trous dans l'expressivité" - des constructions pour lesquelles un langage est incapable d'écrire du code efficace.
supercat

1
@supercat J'ai bien peur que nous soyons issus d'écoles fondamentalement différentes de conception linguistique. Je m'oppose à ce que les langues soient au maximum expressives au prix de la compréhensibilité (ou de l'exactitude). Je préfère avoir un langage restrictif plutôt que permissif.
Konrad Rudolph

1
@thb Oui, bien sûr. Cela rend souvent le code beaucoup plus difficile à lire et à raisonner. Presque chaque fois que quelqu'un publie du code contenant gotosur le site de révision du code, l'élimination de la gotosimplifie considérablement la logique du code.
Konrad Rudolph

1

Le GOTO peut être utilisé, bien sûr, mais il y a une chose plus importante que le style de code, ou si le code est ou non lisible que vous devez avoir à l'esprit lorsque vous l'utilisez: le code à l'intérieur peut ne pas être aussi robuste que vous pense .

Par exemple, regardez les deux extraits de code suivants:

If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)

Un code équivalent avec GOTO

If A == 0 Then GOTO FINAL EndIf
   A = 0
FINAL:
Write("Value of A:" + A)

La première chose que nous pensons est que le résultat des deux bits de code sera cette "valeur de A: 0" (nous supposons une exécution sans parallélisme, bien sûr)

Ce n'est pas correct: dans le premier échantillon, A sera toujours égal à 0, mais dans le deuxième échantillon (avec l'instruction GOTO), A pourrait ne pas être égal à 0. Pourquoi?

La raison en est qu'à partir d'un autre point du programme, je peux insérer un GOTO FINALsans contrôler la valeur de A.

Cet exemple est très évident, mais à mesure que les programmes se compliquent, la difficulté de voir ce genre de choses augmente.

Des documents connexes peuvent être trouvés dans le célèbre article de M. Dijkstra "Un cas contre la déclaration GO TO"


6
C'était souvent le cas dans la vieille école BASIC. Dans les variantes modernes, cependant, vous n'êtes pas autorisé à sauter au milieu d'une autre fonction ... ou dans de nombreux cas, même après la déclaration d'une variable. Fondamentalement (jeu de mots non prévu), les langues modernes ont largement éliminé les GOTO "débridés" dont Dijkstra parlait ... et la seule façon de l'utiliser comme il l'a argumenté, est de commettre certains autres péchés odieux. :)
cHao

1

J'utilise goto dans le cas suivant: lorsque nécessaire pour revenir de fonctions à différents endroits, et avant le retour, une désinitialisation doit être effectuée:

version non goto:

int doSomething (struct my_complicated_stuff *ctx)    
{
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
        db_disconnect(conn);
        return -1;      
        }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        free(temp_data);    
        db_disconnect(conn);    
        return -2;
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        pthread_mutex_unlock(ctx->mutex);
        free(temp_data);
        db_disconnect(conn);        
        return -3;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -4;  
    }

    if (ctx->something_else->additional_check) {
         rsa_free(key); 
         pthread_mutex_unlock(ctx->mutex);
         free(temp_data);
         db_disconnect(conn);       
         return -5;  
    }


    pthread_mutex_unlock(ctx->mutex);
    free(temp_data);    
    db_disconnect(conn);    
    return 0;     
}

version goto:

int doSomething_goto (struct my_complicated_stuff *ctx)
{
    int ret=0;
    db_conn *conn;
    RSA *key;
    char *temp_data;
    conn = db_connect();  


    if (ctx->smth->needs_alloc) {
      temp_data=malloc(ctx->some_size);
      if (!temp_data) {
            ret=-1;
           goto exit_db;   
          }
    }

    ...

    if (!ctx->smth->needs_to_be_processed) {
        ret=-2;
        goto exit_freetmp;      
    }

    pthread_mutex_lock(ctx->mutex);

    if (ctx->some_other_thing->error) {
        ret=-3;
        goto exit;  
    }

    ...

    key=rsa_load_key(....);

    ...

    if (ctx->something_else->error) {
        ret=-4;
        goto exit_freekey; 
    }

    if (ctx->something_else->additional_check) {
        ret=-5;
        goto exit_freekey;  
    }

exit_freekey:
    rsa_free(key);
exit:    
    pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
    free(temp_data);        
exit_db:
    db_disconnect(conn);    
    return ret;     
}

La deuxième version le rend plus facile, lorsque vous devez changer quelque chose dans les instructions de désallocation (chacune est utilisée une fois dans le code), et réduit les chances d'ignorer l'une d'entre elles, lors de l'ajout d'une nouvelle branche. Les déplacer dans une fonction n'aidera pas ici, car la désallocation peut se faire à différents "niveaux".


3
C'est pourquoi nous avons des finallyblocs en C #
John Saunders

^ comme l'a dit @JohnSaunders. Ceci est un exemple de "l'utilisation de goto car un langage n'a pas la construction de contrôle appropriée". CEPENDANT, c'est une odeur de code d'exiger PLUSIEURS points goto près du retour. Il existe un style de programmation qui est plus sûr (plus facile à ne pas bousiller) ET ne nécessite pas de gotos, qui fonctionne très bien même dans les langues sans "finalement": concevez ces appels de "nettoyage" afin qu'ils soient inoffensifs à faire quand aucun nettoyage est nécessaire. Factorisez tout sauf le nettoyage dans une méthode qui utilise la conception à retours multiples. Appelez cette méthode, puis effectuez les appels de nettoyage.
ToolmakerSteve

Notez que l'approche que je décris nécessite un niveau supplémentaire d'appel de méthode (mais uniquement dans un langage qui manque finally). Comme alternative, utilisez gotos, mais point de sortie commun , qui fait toujours tout le nettoyage. Mais chaque méthode de nettoyage peut soit gérer une valeur nulle ou déjà propre, soit être protégée par un test conditionnel, donc sautée lorsqu'elle n'est pas appropriée.
ToolmakerSteve

@ToolmakerSteve ce n'est pas une odeur de code; en fait, c'est un modèle extrêmement courant en C et est considéré comme l'un des moyens les plus valides d'utiliser goto. Vous voulez que je crée 5 méthodes, chacune avec son propre if-test inutile, juste pour gérer le nettoyage de cette fonction? Vous avez maintenant créé du code ET un gonflement des performances. Ou vous pouvez simplement utiliser goto.
muusbolla

@muusbolla - 1) "créer 5 méthodes" - Non. Je propose une méthode unique qui nettoie toutes les ressources qui ont une valeur non nulle. OU voyez mon alternative, qui utilise des gotos qui vont tous même point de sortie, qui a la même logique (ce qui nécessite un supplément si par ressource, comme vous le dites). Mais peu importe, lorsque Cvous avez raison, quelle que soit la raison pour laquelle le code est en C, c'est presque certainement un compromis qui favorise le code le plus "direct". (Ma suggestion traite des situations complexes où une ressource donnée peut ou non avoir été allouée. Mais oui, exagéré dans ce cas.)
ToolmakerSteve

0

Edsger Dijkstra, un informaticien qui a apporté une contribution majeure sur le terrain, était également célèbre pour avoir critiqué l'utilisation de GoTo. Il y a un court article sur son argument sur Wikipedia .


0

Il est pratique pour le traitement de chaîne de caractères de temps en temps.

Imaginez quelque chose comme cet exemple printf-esque:

for cur_char, next_char in sliding_window(input_string) {
    if cur_char == '%' {
        if next_char == '%' {
            cur_char_index += 1
            goto handle_literal
        }
        # Some additional logic
        if chars_should_be_handled_literally() {
            goto handle_literal
        }
        # Handle the format
    }
    # some other control characters
    else {
      handle_literal:
        # Complicated logic here
        # Maybe it's writing to an array for some OpenGL calls later or something,
        # all while modifying a bunch of local variables declared outside the loop
    }
}

Vous pouvez refactoriser cela goto handle_literalen un appel de fonction, mais s'il modifie plusieurs variables locales différentes, vous devrez transmettre des références à chacune à moins que votre langage ne prenne en charge les fermetures mutables. Vous devez toujours utiliser uncontinue instruction (qui est sans doute une forme de goto) après l'appel pour obtenir la même sémantique si votre logique fait que le cas else ne fonctionne pas.

J'ai également utilisé judicieusement des gotos dans des lexers, généralement pour des cas similaires. Vous n'en avez pas besoin la plupart du temps, mais ils sont agréables à avoir pour ces cas étranges.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.