È mai vantaggioso utilizzare ‘goto’ in una lingua che supporta loop e funzioni? Se è così, perché?

Ho sempre avuto l’impressione che goto non dovrebbe mai essere usato se ansible. Mentre leggevo libavcodec (che è scritto in C) l’altro giorno, ho notato molti usi di esso. È mai vantaggioso utilizzare goto in una lingua che supporta loop e funzioni? Se è così, perché?

Ci sono alcuni motivi per usare l’istruzione “goto” di cui sono a conoscenza (alcuni hanno già parlato in questo modo):

Uscendo in modo pulito da una funzione

Spesso in una funzione, è ansible allocare risorse e devono uscire in più posizioni. I programmatori possono semplificare il loro codice inserendo il codice di pulitura delle risorse alla fine della funzione e tutti i “punti di uscita” della funzione goto l’etichetta di pulizia. In questo modo, non è necessario scrivere codice di pulizia in ogni “punto di uscita” della funzione.

Uscita da cicli annidati

Se sei in un ciclo annidato e hai bisogno di uscire da tutti i loop, un goto può renderlo molto più pulito e semplice delle istruzioni break e if-checks.

Miglioramenti delle prestazioni a basso livello

Questo è valido solo nel codice perf-critical, ma le istruzioni goto vengono eseguite molto rapidamente e possono dare una spinta quando si passa attraverso una funzione. Questa è un’arma a doppio taglio, tuttavia, poiché in genere un compilatore non può ottimizzare il codice che contiene gotos.

Si noti che in tutti questi esempi, i gotos sono limitati allo scopo di una singola funzione.

Tutti coloro che sono anti- goto citano, direttamente o indirettamente, l’articolo di GoTo Considerato Nocivo di Edsger Dijkstra a sostegno della loro posizione. Peccato che l’articolo di Dijkstra non abbia praticamente nulla a che fare con il modo in cui le dichiarazioni goto vengono usate in questi giorni e quindi quello che l’articolo dice ha poca o nessuna applicabilità alla scena di programmazione moderna. Il meme goto -less orge ora su una religione, fino alle sue scritture dettate dall’alto, i suoi alti sacerdoti e lo sfiducia (o peggio) degli eretici percepiti.

Metteremo la carta di Dijkstra nel contesto per gettare un po ‘di luce sull’argomento.

Quando Dijkstra scrisse il suo articolo, i linguaggi popolari del tempo erano quelli procedurali non strutturati come BASIC, FORTRAN (i dialetti precedenti) e vari linguaggi di assemblaggio. Era abbastanza comune per le persone che usano i linguaggi di livello superiore saltare tutta la loro base di codice in fili di esecuzione contorti e contorti che hanno dato origine al termine “codice spaghetti”. Puoi vederlo passando al classico gioco Trek scritto da Mike Mayfield e cercando di capire come funzionano le cose. Prenditi qualche minuto per vederlo.

Questo è “l’uso sfrenato del go to statement” di cui Dijkstra si scaglia contro nel suo lavoro nel 1968. QUESTO è l’ambiente in cui visse e che lo portò a scrivere quel documento. La possibilità di saltare ovunque ti piace nel tuo codice in qualsiasi momento ti è piaciuto è stato quello che stava criticando e chiedendo di essere fermato. Paragonare questo ai poteri anemici del goto in C o ad altri linguaggi più moderni è semplicemente risibile.

Posso già sentire i canti sollevati dei cultisti mentre affrontano l’eretico. “Ma,” canteranno, “puoi rendere il codice molto difficile da leggere con goto in C.” O si? È ansible rendere il codice molto difficile da leggere senza goto . Come questo:

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

Non è un goto in vista, quindi deve essere facile da leggere, giusto? O che ne pensi di questo:

 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 

No goto lì neanche. Deve quindi essere leggibile.

Qual è il mio punto con questi esempi? Non sono le caratteristiche del linguaggio che rendono il codice illeggibile e non gestibile. Non è la syntax che lo fa. Sono cattivi programmatori che causano questo. E i cattivi programmatori, come puoi vedere in questo articolo, possono rendere illeggibili e inutilizzabili le funzionalità linguistiche. Come i loop per lassù. (Puoi vederli, vero?)

Ora, per essere onesti, alcuni costrutti linguistici sono più facili da abusare di altri. Se sei un programmatore C, tuttavia, guarderei molto più da vicino a circa il 50% degli usi di #define molto prima di andare in una crociata contro il goto !

Quindi, per coloro che si sono presi la briga di leggere fino a qui, ci sono diversi punti chiave da notare.

  1. Il documento di Dijkstra sulle dichiarazioni goto stato scritto per un ambiente di programmazione in cui goto era molto più potenzialmente dannoso di quanto non sia nella maggior parte delle lingue moderne che non sono un assemblatore.
  2. Eliminare automaticamente tutti gli usi di goto causa di questo è razionale quanto dire "Ho provato a divertirmi una volta ma non mi è piaciuto così ora sono contrario".
  3. Esistono usi legittimi delle dichiarazioni goto moderne (anemiche) nel codice che non possono essere adeguatamente sostituite da altri costrutti.
  4. Esistono, ovviamente, usi illegittimi delle stesse dichiarazioni.
  5. Vi sono anche usi illegittimi delle moderne dichiarazioni di controllo come l'abominio " godo " in cui un loop di do sempre falso viene interrotto dall'uso di break al posto di un goto . Questi sono spesso peggio di un uso giudizioso del goto .

Tieni a mente mentre mi stai votando con un -1 dopo l'altro che ho usato goto nel mio codice (non assemblatore) precisamente 3 volte negli ultimi 15-20 anni.

Attendo il diluvio di urla indignate e -1 voti con il fiato sospeso.

Obbedire alle best practice in modo cieco non è una buona pratica. L’idea di evitare istruzioni goto come forma primaria di controllo del stream è di evitare di produrre codice spaghetti illeggibile. Se usati con parsimonia nei posti giusti, a volte possono essere il modo più semplice e chiaro di esprimere un’idea. Walter Bright, il creatore del compilatore C ++ di Zortech e il linguaggio di programmazione D, li usa spesso, ma con giudizio. Anche con le istruzioni goto , il suo codice è ancora perfettamente leggibile.

In conclusione: evitare goto per evitare di goto è inutile. Quello che vuoi veramente evitare è produrre codice illeggibile. Se il tuo codice goto -leggibile è leggibile, non c’è niente di sbagliato in questo.

Poiché goto fa ragionare sul stream del programma hard 1 (noto anche come “codice spaghetti”), goto viene generalmente utilizzato solo per compensare le funzionalità mancanti: l’uso di goto può essere effettivamente accettabile, ma solo se la lingua non offre una struttura più strutturata variante per ottenere lo stesso objective. Prendi l’esempio di Doubt:

La regola con goto che usiamo è che goto va bene per saltare in avanti verso un punto di pulitura a uscita singola in una funzione.

Questo è vero, ma solo se il linguaggio non consente la gestione strutturata delle eccezioni con il codice cleanup (come RAII o finally ), che fa lo stesso lavoro (dato che è stato appositamente creato per farlo), o quando c’è una buona ragione non utilizzare una gestione delle eccezioni strutturata (ma non si avrà mai questo caso se non a un livello molto basso).

Nella maggior parte delle altre lingue, l’unico uso accettabile di goto è quello di uscire da cicli annidati. E anche lì è quasi sempre meglio sollevare il ciclo esterno in un proprio metodo e utilizzare invece il return .

Oltre a questo, goto è un segno che non è stato pensato abbastanza sul particolare pezzo di codice.


1 Lingue moderne che supportano goto implementano alcune restrizioni (es. goto non può saltare dentro o fuori dalle funzioni) ma il problema rimane fondamentalmente lo stesso.

Per inciso, lo stesso vale ovviamente anche per altre funzionalità linguistiche, in particolare le eccezioni. E di solito sono in vigore regole rigide per utilizzare queste funzioni solo dove indicato, come la regola di non usare eccezioni per controllare il stream di programmi non eccezionale.

Bene, c’è una cosa che è sempre peggio di quella di goto's ; strano uso di altri operatori di programflow per evitare un goto:

Esempi:

  // 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 

Nella dichiarazione dell’interruttore C # non si consente fall-through . Quindi goto viene utilizzato per trasferire il controllo a un’etichetta specifica del caso o all’etichetta predefinita .

Per esempio:

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

Modifica: esiste una sola eccezione per la regola “no fall-through”. Fall-through è consentito se un’istruzione case non ha codice.

#ifdef TONGUE_IN_CHEEK

Perl ha un goto che ti permette di implementare le chiamate tail di poveri. 😛

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

#endif

Ok, quindi non ha nulla a che fare con il goto di C. Più seriamente, sono d'accordo con gli altri commenti sull'utilizzo di goto per le pulizie, o per l'implementazione del dispositivo di Duff , o simili. Si tratta di utilizzare, non abusare.

(Lo stesso commento può essere applicato a longjmp , eccezioni, call/cc e simili --- hanno usi legittimi, ma possono essere facilmente abusati. Ad esempio, lanciare un'eccezione puramente per sfuggire a una struttura di controllo profondamente annidata, completamente circostanze non eccezionali).

Ho scritto più di poche righe di linguaggio assembly nel corso degli anni. Alla fine, tutti i linguaggi di alto livello si compongono fino al gotos. Ok, chiamali “rami” o “salti” o qualsiasi altra cosa, ma sono gotos. Qualcuno può scrivere assembler senza goto?

Ora certo, puoi indicare a un programmatore Fortran, C o BASIC che eseguire la sommossa con gotos è una ricetta per spaghetti alla bolognese. La risposta tuttavia non è di evitarli, ma di usarli con attenzione.

Un coltello può essere usato per preparare cibo, liberare qualcuno o uccidere qualcuno. Facciamo senza coltelli per paura di questi ultimi? Allo stesso modo il goto: usato con noncuranza ostacola, usato con attenzione aiuta.

Dai un’occhiata a Quando usare Goto durante la programmazione in C :

Anche se l’uso di goto è quasi sempre una ctriggers pratica di programmazione (sicuramente puoi trovare un modo migliore di fare XYZ), ci sono momentjs in cui non è davvero una ctriggers scelta. Alcuni potrebbero persino obiettare che, quando è utile, è la scelta migliore.

La maggior parte di ciò che ho da dire su goto si applica solo a C. Se stai usando C ++, non c’è motivo di usare goto al posto delle eccezioni. In C, tuttavia, non si ha il potere di un meccanismo di gestione delle eccezioni, quindi se si desidera separare la gestione degli errori dal resto della logica del programma e si desidera evitare di riscrivere il codice di pulizia più volte nel codice, quindi goto può essere una buona scelta.

Cosa voglio dire? Potresti avere un codice simile a questo:

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

Questo va bene finché non ti rendi conto che è necessario modificare il codice di pulizia. Quindi devi passare e apportare 4 modifiche. Ora, potresti decidere di incapsulare tutto il cleanup in una singola funzione; non è una ctriggers idea Ma vuol dire che dovrai fare attenzione con i puntatori: se prevedi di liberare un puntatore nella tua funzione di pulizia, non c’è modo di impostarlo in modo che punti a NULL a meno che non passi un puntatore a un puntatore. In molti casi, non si utilizzerà di nuovo quel puntatore in ogni caso, quindi potrebbe non essere una preoccupazione importante. D’altra parte, se aggiungi un nuovo puntatore, un handle di file o un’altra cosa che deve essere ripulita, dovrai cambiare di nuovo la tua funzione di pulizia; e quindi dovrai cambiare gli argomenti per quella funzione.

Usando goto , lo sarà

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

Il vantaggio qui è che il tuo codice successivo alla fine ha accesso a tutto ciò di cui avrà bisogno per eseguire la pulizia, e sei riuscito a ridurre considerevolmente il numero di punti di cambio. Un altro vantaggio è che hai passato da più punti di uscita per la tua funzione a uno solo; non c’è alcuna possibilità che tu possa accidentalmente tornare dalla funzione senza ripulire.

Inoltre, dato che goto viene usato solo per saltare a un singolo punto, non è come se si stesse creando una massa di codice spaghetti che salta avanti e indietro nel tentativo di simulare le chiamate di funzione. Piuttosto, goto aiuta effettivamente a scrivere codice più strutturato.


In una parola, goto dovrebbe sempre essere usato con parsimonia, e come ultima risorsa – ma c’è un tempo e un posto per farlo. La domanda non dovrebbe essere “devi usarla” ma “è la scelta migliore” per usarla.

Trovo strano che alcune persone arrivino al punto di fornire un elenco di casi in cui goto è accettabile, dicendo che tutti gli altri usi sono inaccettabili. Pensi davvero di conoscere ogni caso in cui goto è la scelta migliore per esprimere un algoritmo?

Per illustrare, ti darò un esempio che nessuno qui ha ancora mostrato:

Oggi stavo scrivendo il codice per inserire un elemento in una tabella hash. La tabella hash è una cache di calcoli precedenti che possono essere sovrascritti a piacimento (influenzando le prestazioni ma non la correttezza).

Ogni bucket della tabella hash ha 4 slot e ho una serie di criteri per decidere quale elemento sovrascrivere quando un bucket è pieno. In questo momento questo significa fare fino a tre passaggi attraverso un bucket, come questo:

 // 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 

Ora se non avessi usato goto, come sarebbe questo codice?

Qualcosa come questo:

 // 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 

Sembrerebbe sempre peggio se vengono aggiunti più passaggi, mentre la versione con goto mantiene sempre lo stesso livello di indentazione ed evita l'uso di dichiarazioni spurie se il cui risultato è implicito nell'esecuzione del ciclo precedente.

Quindi c'è un altro caso in cui goto rende il codice più pulito e più facile da scrivere e capire ... Sono sicuro che ce ne sono molti altri, quindi non fingere di conoscere tutti i casi in cui goto è utile, dissentendo quelli buoni che non potresti pensare.

La regola con goto che usiamo è che goto va bene per saltare in avanti verso un punto di pulitura a uscita singola in una funzione. In funzioni veramente complesse rilassiamo quella regola per permettere ad altri salti in avanti. In entrambi i casi evitiamo di annidare in profondità dichiarazioni che spesso si verificano con il controllo del codice di errore, il che aiuta a migliorare la leggibilità e la manutenzione.

Uno dei motivi per cui goto è pessimo, oltre allo stile di codifica è che puoi usarlo per creare loop sovrapposti , ma non annidati :

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

Ciò creerebbe la struttura bizzarra, ma possibilmente legale, del stream di controllo in cui è ansible una sequenza come (a, b, c, b, a, b, a, b, …), il che rende infelici gli hacker del compilatore. Apparentemente ci sono una serie di astuti trucchi di ottimizzazione che si basano su questo tipo di struttura che non si verifica. (Dovrei controllare la mia copia del libro del drago …) Il risultato di questo potrebbe (usando alcuni compilatori) essere che altre ottimizzazioni non sono fatte per il codice che contiene goto s.

Potrebbe essere utile se lo sapete , “oh, a proposito”, accade per convincere il compilatore ad emettere un codice più veloce. Personalmente, preferirei provare a spiegare al compilatore cosa è probabile e cosa non lo è prima di usare un trucco come goto, ma probabilmente, potrei anche provare goto prima di hackerare l’assemblatore.

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.

Ecco un rapido esempio:

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

Se è così, perché?

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 compiler-level support and checks for common constructs/patterns: it’s easier to use them wrong and introduce bugs without compile-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 compiler 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 compiler-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 compiler 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 compiler can’t easily figure out, like gotos to computed addresses(**), it’s not any harder for the compiler 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 compile time. The usual reason is logic code like the following. Dato

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

It’s just as hard for a compiler 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 tried 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 contributions 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