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

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

Il y a quelques raisons d’utiliser la déclaration “goto” dont je suis au courant (certains en ont déjà parlé):

Sortir d’une fonction proprement

Souvent, dans une fonction, vous pouvez allouer des ressources et devoir sortir dans plusieurs endroits. Les programmeurs peuvent simplifier leur code en mettant le code de nettoyage des ressources à la fin de la fonction, et tous les “points de sortie” de la fonction obtiendraient l’étiquette de nettoyage. De cette façon, vous n’avez pas à écrire de code de nettoyage à chaque “sharepoint sortie” de la fonction.

Quitter les boucles nestedes

Si vous êtes dans une boucle nestede et que vous avez besoin de sortir de toutes les boucles, un goto peut rendre cela plus clair et plus simple que les instructions break et if-checks.

Amélioration des performances de bas niveau

Ceci n’est valide que dans le code critique, mais les instructions goto s’exécutent très rapidement et peuvent vous aider à vous déplacer dans une fonction. Ceci est une arme à double tranchant, cependant, car un compilateur ne peut généralement pas optimiser le code qui contient des gotos.

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

Tout le monde qui est anti- goto cite, directement ou indirectement, l’article de GoTo considéré comme nocif d’ Edsger Dijkstra pour étayer sa position. Dommage que l’article de Dijkstra n’ait pratiquement rien à voir avec la manière dont les goto sont utilisées de nos jours et que ce que dit l’article ne s’applique donc guère à la scène de programmation moderne. Le mème goto -less se rapproche maintenant d’une religion, jusque dans ses écritures dictées d’en haut, ses grands prêtres et les fugitifs (ou pire) des hérétiques perçus.

Mettons le papier de Dijkstra dans son contexte pour faire la lumière sur le sujet.

Lorsque Dijkstra a écrit son article, les langages populaires de l’époque étaient des langages procéduraux non structurés comme BASIC, FORTRAN (les dialectes antérieurs) et divers langages d’assemblage. Il était assez courant que les personnes utilisant les langages de niveau supérieur sautent partout dans leur base de code dans des fils d’exécution tordus et contorsionnés qui ont donné naissance au terme “code spaghetti”. Vous pouvez voir cela en sautant sur le jeu classique de Trek écrit par Mike Mayfield et en essayant de comprendre comment les choses fonctionnent. Prenez quelques instants pour regarder ça.

C’est «l’utilisation effrénée de la déclaration de départ» que Dijkstra critiquait dans son article de 1968. C’est l’environnement dans lequel il a vécu et qui l’a amené à écrire cet article. La possibilité de sauter n’importe où dans votre code à n’importe quel moment était ce qu’il critiquait et exigeait qu’on l’arrête. Comparer cela aux pouvoirs anémiques du goto en C ou d’autres langages plus modernes est simplement risible.

Je peux déjà entendre les chants élevés des cultistes comme ils font face à l’hérétique. “Mais, ils vont chanter,” vous pouvez rendre le code très difficile à lire avec goto dans C. ” Oh oui? Vous pouvez 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 goto en 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 

Pas de goto non plus. Il doit donc être lisible.

Quel est mon point avec ces exemples? Ce ne sont pas des fonctionnalités de langage qui rendent le code illisible et impossible à maintenir. Ce n'est pas la syntaxe qui le fait. Ce sont les mauvais programmeurs qui causent cela. Et les mauvais programmeurs, comme vous pouvez le voir dans cet élément ci-dessus, peuvent rendre toute fonctionnalité de langue illisible et inutilisable. Comme le for boucles là-haut. (Vous pouvez les voir, non?)

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

Donc, pour ceux qui ont pris la peine de lire aussi loin, il y a plusieurs points importants à noter.

  1. L'article de Dijkstra sur les déclarations goto été écrit pour un environnement de programmation où le goto était beaucoup plus dommageable que dans la plupart des langages modernes qui ne sont pas un assembleur.
  2. Jeter automatiquement toutes les utilisations de goto cause de cela est à peu près aussi rationnel que de dire "j'ai essayé de m'amuser une fois mais je ne l'ai pas aimé alors maintenant je suis contre".
  3. Il y a des utilisations légitimes des déclarations modernes (anémiques) 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 telles que l'abomination " godo " où une boucle toujours fausse est utilisée en utilisant une break à la place d'un goto . Celles-ci sont souvent pires que l'utilisation judicieuse du goto .

Gardez à l'esprit que lorsque vous votez avec un -1 après l'autre, j'ai utilisé goto dans mon propre code (non assembleur) précisément 3 fois au cours des 15 à 20 dernières années.

J'attends le flot de cris outragés et de -1 votes avec un souffle retentissant.

Obéir aveuglément aux meilleures pratiques n’est pas une bonne pratique. L’idée d’éviter les goto comme principale forme de contrôle de stream est d’éviter de produire du code spaghetti illisible. 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 goto , son code est toujours parfaitement lisible.

Bottom line: éviter goto pour éviter goto est inutile. Ce que vous voulez vraiment éviter, c’est de produire du code illisible. Si votre code goto -laden est lisible, alors il n’y a rien de mal à cela.

Puisque goto rend le raisonnement sur le stream de programme hard 1 (alias «code spaghetti»), goto est généralement utilisé uniquement pour compenser les fonctionnalités manquantes: l’utilisation de goto peut en fait être acceptable, mais uniquement si la langue n’offre pas un format plus structuré variante pour obtenir le même objective. Prenons l’exemple du doute:

La règle avec goto que nous utilisons est que goto peut aller de l’avant à un sharepoint nettoyage de sortie unique dans une fonction.

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

Dans la plupart des autres langues, la seule utilisation acceptable de goto est de sortir des boucles nestedes. Et même là, il est presque toujours préférable de placer la boucle externe dans une méthode propre et d’utiliser plutôt le return .

En dehors de cela, goto est un signe que le code n’a pas été suffisamment réfléchi.


1 Les langages modernes qui prennent en charge ne doivent pas implémenter certaines ressortingctions (par exemple, goto peut ne pas entrer ou sortir des fonctions) mais le problème rest fondamentalement le même.

Incidemment, il en va de même pour d’autres fonctionnalités linguistiques, notamment les exceptions. Et il existe généralement des règles ssortingctes pour utiliser ces fonctionnalités uniquement lorsque cela est indiqué, telles que la règle de ne pas utiliser les exceptions pour contrôler les stream de programmes non exceptionnels.

Eh bien, il y a une chose qui est toujours pire que goto's ; utilisation étrange d’autres opérateurs de stream 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 

Dans l’instruction C # switch , n’autorisez pas l’ interruption . Donc, goto est utilisé pour transférer le contrôle à une étiquette de commutateur 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 sur la règle “no fall-through”. Fall-through est autorisé si une déclaration de cas n’a pas de code.

#ifdef TONGUE_IN_CHEEK

Perl a un goto qui vous permet d’implémenter les appels de queue de l’homme pauvre. 😛

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

#endif

Okay, alors ça n'a rien à voir avec C's goto . Plus sérieusement, je suis d’accord avec les autres commentaires concernant l’utilisation de goto pour les nettoyages, ou pour l’implémentation de l’appareil Duff , etc. Il s'agit d'utiliser, de ne pas abuser.

(Le même commentaire peut s’appliquer à longjmp , aux exceptions, aux call/cc et autres --- ils ont des utilisations légitimes, mais peuvent être facilement abusés. Par exemple, lancer une exception uniquement pour échapper à une structure de contrôle profondément nestede) circonstances non exceptionnelles.)

J’ai écrit plus de quelques lignes de langage d’assemblage au fil des ans. En fin de compte, chaque langage de haut niveau est compilé au mieux. Ok, appelez-les “twigs” ou “sauts” ou autre chose, mais ils sont gotos. Quelqu’un peut-il écrire assembleur goto-moins?

Bien sûr, vous pouvez indiquer à un programmeur Fortran, C ou BASIC que lancer des émeutes avec des gotos est une recette de spaghetti bolognaise. La réponse n’est toutefois 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é négligemment, il gêne, utilisé avec précaution, il aide.

Jetez un oeil à 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 soutenir que, lorsque c’est utile, c’est le meilleur choix.

La plupart de ce que j’ai à dire à propos de 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 rest de la logique de votre programme et éviter de réécrire plusieurs fois le code de nettoyage dans votre code, alors goto peut être un bon choix.

Qu’est ce que je veux dire? Vous pourriez avoir du 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]; } 

Cela va jusqu’à ce que vous réalisiez que vous devez changer votre code de nettoyage. Ensuite, vous devez effectuer 4 changements. Maintenant, vous pouvez 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 être prudent avec les pointeurs – si vous prévoyez de libérer un pointeur dans votre fonction de nettoyage, vous ne pouvez pas le définir pour pointer sur NULL, sauf si vous passez un pointeur sur un pointeur. Dans beaucoup de 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 nécessitant un nettoyage, vous devrez modifier à nouveau votre fonction de nettoyage. et ensuite vous devrez changer les arguments pour cette fonction.

En utilisant goto , ce 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 est que le code suivant a access à tout ce dont il aura besoin pour effectuer le nettoyage, et vous avez réussi à réduire considérablement le nombre de points de modification. 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, comme goto n’est utilisé que pour sauter à un seul point, ce n’est pas comme si vous créiez une masse de code spaghetti pour tenter de simuler des appels de fonctions. Au contraire, goto aide réellement à écrire du code plus structuré.


En un mot, goto doit toujours être utilisé avec parcimonie, et en dernier recours – mais il y a un temps et une place pour cela. La question ne devrait pas être “faut-il l’utiliser” mais “est-ce le meilleur choix” pour l’utiliser.

Je trouve cela drôle que certaines personnes vont 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 mon propos, 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 dispose de 4 emplacements, et je dispose de nombreux critères pour décider quel élément remplacer lorsque le compartiment est plein. À l’heure actuelle, cela signifie que 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 

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

Donc, il y a 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 d'autres, alors ne prétendez pas connaître tous les cas où goto est utile. ne pense pas à.

La règle avec goto que nous utilisons est que goto peut aller de l’avant à un sharepoint nettoyage de sortie unique dans une fonction. Dans des fonctions très complexes, nous assouplissons cette règle pour permettre d’autres sauts en avant. Dans les deux cas, nous évitons les instructions nestedes qui se produisent souvent lors de la vérification du code d’erreur, ce qui facilite la lecture et la maintenance.

L’une des raisons pour lesquelles goto est mauvais, outre le style de codage, est que vous pouvez l’utiliser pour créer des boucles superposées , mais non nestedes :

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

Cela créerait une structure de stream de contrôle bizarre, mais éventuellement légale, où une séquence comme (a, b, c, b, a, b, a, b, …) est possible, ce qui rend les pirates informatiques malheureux. Apparemment, il existe plusieurs astuces d’optimisation astucieuses qui ne dépendent pas de ce type de structure. (Je devrais vérifier ma copie du livre de dragons …) Le résultat de ceci pourrait (en utilisant certains compilateurs) être que d’autres optimisations ne sont pas faites pour le code qui contient goto s.

Cela peut être utile si vous le savez , “oh, au fait”, il arrive que le compilateur émette un code plus rapide. Personnellement, je préférerais essayer d’expliquer au compilateur ce qui est probable et ce qui ne l’est pas avant d’utiliser un truc comme goto, mais sans doute, je pourrais aussi essayer goto avant de pirater un assembleur.

The most thoughtful and thorough discussion of goto statements, their legitimate uses, and alternative constructs that can be used in place of “virtuous goto statements” but can be abused as easily as goto statements, is Donald Knuth’s article ” Structured Programming with goto Statements “, in the December 1974 Computing Surveys (volume 6, no. 4. pp. 261 – 301).

Not surprisingly, some aspects of this 39-year old paper are dated: Orders-of-magnitude increases in processing power make some of Knuth’s performance improvements unnoticeable for moderately sized problems, and new programming-language constructs have been invented since then. (For example, try-catch blocks subsume Zahn’s Construct, although they are rarely used in that way.) But Knuth covers all sides of the argument, and should be required reading before anyone rehashes the issue yet again.

In a Perl module, you occasionally want to create subroutines or closures on the fly. The thing is, that once you have created the subroutine, how do you get to it. You could just call it, but then if the subroutine uses caller() it won’t be as helpful as it could be. That is where the goto &subroutine variation can be helpful.

Voici un exemple rapide:

 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. } 

You can also use this form of goto to provide a rudimentary form of tail-call optimization.

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

( In Perl 5 version 16 that would be better written as goto __SUB__; )

There is a module that will import a tail modifier and one that will import recur if you don't like using this form of 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 ); } 

Most of the other reasons to use goto are better done with other keywords.

Like redo ing a bit of code:

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

Or going to the last of a bit of code from multiple places:

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

Si oui, pourquoi?

C has no multi-level/labelled break, and not all control flows can be easily modelled with C’s iteration and decision primitives. gotos go a long way towards redressing these flaws.

Sometimes it’s clearer to use a flag variable of some kind to effect a kind of pseudo-multi-level break, but it’s not always superior to the goto (at least a goto allows one to easily determine where control goes to, unlike a flag variable), and sometimes you simply don’t want to pay the performance price of flags/other contortions to avoid the goto.

libavcodec is a performance-sensitive piece of code. Direct expression of the control flow is probably a priority, because it’ll tend to run better.

Just as well no one ever implemented the “COME FROM” statement….

I find the do{} while(false) usage utterly revolting. It is conceivable might convince me it is necessary in some odd case, but never that it is clean sensible code.

If you must do some such loop, why not make the dependence on the flag variable explicit?

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

The GOTO can be used, of course, but there is one more important thing than the code style, or if the code is or not readable that you must have in mind when you use it: the code inside may not be as robust as you think .

For instance, look at the following two code snippets:

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

An equivalent code with GOTO

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

The first thing we think is that the result of both bits of code will be that “Value of A: 0” (we suppose an execution without parallelism, of course)

That’s not correct: in the first sample, A will always be 0, but in the second sample (with the GOTO statement) A might not be 0. Why?

The reason is because from another point of the program I can insert a GOTO FINAL without controlling the value of A.

This example is very obvious, but as programs get more complicated, the difficulty of seeing those kind of things increases.

Related material can be found into the famous article from Mr. Dijkstra “A case against the GO TO statement”

1) The most common use of goto that I know of is emulating exception handling in languages that don’t offer it, namely in C. (The code given by Nuclear above is just that.) Look at the Linux source code and you’ll see a bazillion gotos used that way; there were about 100,000 gotos in Linux code according to a quick survey conducted in 2013: http://blog.regehr.org/archives/894 . Goto usage is even mentioned in the Linux coding style guide: https://www.kernel.org/doc/Documentation/CodingStyle . Just like object-oriented programming is emulated using structs populated with function pointers, goto has its place in C programming. So who is right: Dijkstra or Linus (and all Linux kernel coders)? It’s theory vs. practice basically.

There is however the usual gotcha for not having comstackr-level support and checks for common constructs/patterns: it’s easier to use them wrong and introduce bugs without comstack-time checks. Windows and Visual C++ but in C mode offer exception handling via SEH/VEH for this very reason: exceptions are useful even outside OOP languages, ie in a procedural language. But the comstackr can’t always save your bacon, even if it offers syntactic support for exceptions in the language. Consider as example of the latter case the famous Apple SSL “goto fail” bug, which just duplicated one goto with disastrous consequences ( 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 

You can have exactly the same bug using comstackr-supported exceptions, eg in C++:

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

But both variants of the bug can be avoided if the comstackr analyzes and warns you about unreachable code. For example compiling with Visual C++ at the /W4 warning level finds the bug in both cases. Java for instance forbids unreachable code (where it can find it!) for a pretty good reason: it’s likely to be a bug in the average Joe’s code. As long as the goto construct doesn’t allow targets that the comstackr can’t easily figure out, like gotos to computed addresses(**), it’s not any harder for the comstackr to find unreachable code inside a function with gotos than using Dijkstra-approved code.

(**) Footnote: Gotos to computed line numbers are possible in some versions of Basic, eg GOTO 10*x where x is a variable. Rather confusingly, in Fortran “computed goto” refers to a construct that is equivalent to a switch statement in C. Standard C doesn’t allow computed gotos in the language, but only gotos to statically/syntactically declared labels. GNU C however has an extension to get the address of a label (the unary, prefix && operator) and also allows a goto to a variable of type void*. See https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html for more on this obscure sub-topic. The rest of this post ins’t concerned with that obscure GNU C feature.

Standard C (ie not computed) gotos are not usually the reason why unreachable code can’t be found at comstack time. The usual reason is logic code like the following. Donné

 int computation1() { return 1; } int computation2() { return computation1(); } 

It’s just as hard for a comstackr to find unreachable code in any of the following 3 constructs:

 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&) { } } 

(Excuse my brace-related coding style, but I sortinged to keep the examples as compact as possible.)

Visual C++ /W4 (even with /Ox) fails to find unreachable code in any of these, and as you probably know the problem of finding unreachable code is undecidable in general. (If you don’t believe me about that: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )

As a related issue, the C goto can be used to emulate exceptions only inside the body of a function. The standard C library offers a setjmp() and longjmp() pair of functions for emulating non-local exits/exceptions, but those have some serious drawbacks compared to what other languages offer. The Wikipedia article http://en.wikipedia.org/wiki/Setjmp.h explains fairly well this latter issue. This function pair also works on Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), but hardly anyone uses them there because SEH/VEH is superior. Even on Unix, I think setjmp and longjmp are very seldom used.

2) I think the second most common use of goto in C is implementing multi-level break or multi-level continue, which is also a fairly uncontroversial use case. Recall that Java doesn’t allow goto label, but allows break label or continue label. According to http://www.oracle.com/technetwork/java/simple-142616.html , this is actually the most common use case of gotos in C (90% they say), but in my subjective experience, system code tends to use gotos for error handling more often. Perhaps in scientific code or where the OS offers exception handling (Windows) then multi-level exits are the dominant use case. They don’t really give any details as to the context of their survey.

Edited to add: it turns out these two use patterns are found in the C book of Kernighan and Ritchie, around page 60 (depending on edition). Another thing of note is that both use cases involve only forward gotos. And it turns out that MISRA C 2012 edition (unlike the 2004 edition) now permits gotos, as long as they are only forward ones.

In Perl, use of a label to “goto” from a loop – using a “last” statement, which is similar to break.

This allows better control over nested loops.

The traditional goto label is supported too, but I’m not sure there are too many instances where this is the only way to achieve what you want – subroutines and loops should suffice for most cases.

The problem with ‘goto’ and the most important argument of the ‘goto-less programming’ movement is, that if you use it too frequently your code, although it might behave correctly, becomes unreadable, unmaintainable, unreviewable etc. In 99.99% of the cases ‘goto’ leads to spaghetti code. Personally, I cannot think of any good reason as to why I would use ‘goto’.

Edsger Dijkstra, a computer scientist that had major consortingbutions on the field, was also famous for criticizing the use of GoTo. There’s a short article about his argument on Wikipedia .

I use goto in the following case: when needed to return from funcions at different places, and before return some uninitialization needs to be done:

non-goto version:

 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; } 

goto version:

 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; } 

The second version makes it easier, when you need to change something in the deallocation statements (each is used once in the code), and reduces the chance to skip any of them, when adding a new branch. Moving them in a function will not help here, because the deallocation can be done at different “levels”.

Some say there is no reason for goto in C++. Some say that in 99% cases there are better alternatives. To be concrete, here’s an example where goto leads to a nice code, something like enhanced do-while loop:

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

Compare it to goto-free code:

 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; 

I see these differences:

  • nested {} block is needed (albeit do {...} while looks more familiar)
  • extra loop variable is needed, used in four places
  • it takes longer time to read and understand the work with the loop
  • the loop does not hold any data, it just controls the flow of the execution, which is less comprehensible than simple label

The point is that goto can be easily misused, but goto itself is not to blame. Note that label has function scope in C++, so it does not pollute global scope like in pure assembly, in which overlapping loops has its place and are very common - like in the following code for 8051, where 7segment display is connected to P1. The program loops lightning segment around:

 ; P1 states loops ; 11111110 < - ; 11111101 | ; 11111011 | ; 11110111 | ; 11101111 | ; 11011111 | ; |_________| again: MOV P1,#11111110b ACALL delay loop: MOV A,P1 RL A MOV P1,A ACALL delay JNB P1.5, again SJMP loop