1) L'uso più comune di goto che conosco è emulare la gestione delle eccezioni in linguaggi che non lo offrono, in particolare in C. (Il codice fornito da Nuclear sopra è proprio questo.) Guarda il codice sorgente di Linux e tu ' Vedrò un bazillion goto usato in quel modo; c'erano circa 100.000 goto nel codice Linux secondo un rapido sondaggio condotto nel 2013: http://blog.regehr.org/archives/894 . L'utilizzo di Goto è anche menzionato nella guida allo stile di codifica Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Proprio come la programmazione orientata agli oggetti viene emulata usando strutture popolate con puntatori a funzioni, goto ha il suo posto nella programmazione C. Quindi chi ha ragione: Dijkstra o Linus (e tutti i codificatori del kernel Linux)? Fondamentalmente è teoria contro pratica.
Esiste tuttavia il solito gotcha per non avere supporto a livello di compilatore e controlli per costrutti / schemi comuni: è più facile usarli in modo errato e introdurre bug senza controlli in fase di compilazione. Windows e Visual C ++ ma in modalità C offrono la gestione delle eccezioni tramite SEH / VEH proprio per questo: le eccezioni sono utili anche al di fuori dei linguaggi OOP, ovvero in un linguaggio procedurale. Ma il compilatore non può sempre salvare la tua pancetta, anche se offre supporto sintattico per le eccezioni nella lingua. Considera come esempio di quest'ultimo caso il famoso bug "goto fail" di Apple SSL, che ha appena duplicato un goto con conseguenze disastrose ( 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
Puoi avere esattamente lo stesso bug usando le eccezioni supportate dal compilatore, ad esempio in C ++:
struct Fail {};
try {
if (something())
throw Fail();
throw Fail(); // copypasta bug
printf("Never reached\n");
}
catch (Fail&) {
// control jumps here
}
Ma entrambe le varianti del bug possono essere evitate se il compilatore analizza e ti avverte di un codice non raggiungibile. Ad esempio la compilazione con Visual C ++ a livello di avviso / W4 trova il bug in entrambi i casi. Java, per esempio, proibisce il codice irraggiungibile (dove può trovarlo!) Per una ragione abbastanza buona: è probabile che sia un bug nel codice medio di Joe. Fintanto che il costrutto goto non consente obiettivi che il compilatore non può facilmente capire, come goto per indirizzi calcolati (**), non è più difficile per il compilatore trovare un codice irraggiungibile all'interno di una funzione con goto che usare Dijkstra codice approvato.
(**) Nota a piè di pagina: sono possibili goto a numeri di riga calcolati in alcune versioni di Basic, ad es. GOTO 10 * x dove x è una variabile. Piuttosto confusamente, in Fortran "goto calcolato" si riferisce a un costrutto equivalente a un'istruzione switch in C. Lo standard C non consente goto calcolati nella lingua, ma solo goto a etichette dichiarate staticamente / sintatticamente. GNU C ha tuttavia un'estensione per ottenere l'indirizzo di un'etichetta (operatore unario, prefisso &&) e consente anche di passare a una variabile di tipo void *. Vedi https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html per ulteriori informazioni su questo oscuro argomento secondario. Il resto di questo post non si preoccupa di quella oscura funzionalità di GNU C.
Le gotos standard C (cioè non calcolate) non sono in genere il motivo per cui non è possibile trovare il codice non raggiungibile al momento della compilazione. Il solito motivo è il codice logico come il seguente. Dato
int computation1() {
return 1;
}
int computation2() {
return computation1();
}
È altrettanto difficile per un compilatore trovare il codice non raggiungibile in uno dei seguenti 3 costrutti:
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&) {
}
}
(Scusa il mio stile di codifica correlato alla parentesi graffa, ma ho cercato di mantenere gli esempi il più compatti possibile.)
Visual C ++ / W4 (anche con / Ox) non riesce a trovare il codice irraggiungibile in nessuno di questi e, come probabilmente saprai, il problema di trovare un codice irraggiungibile in generale è indecidibile. (Se non ci credi: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )
Come problema correlato, C goto può essere usato per emulare eccezioni solo all'interno del corpo di una funzione. La libreria C standard offre una coppia di funzioni setjmp () e longjmp () per emulare uscite / eccezioni non locali, ma queste presentano alcuni seri inconvenienti rispetto a ciò che offrono altre lingue. L'articolo di Wikipedia http://en.wikipedia.org/wiki/Setjmp.h spiega abbastanza bene quest'ultima questione. Questa coppia di funzioni funziona anche su Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), ma quasi nessuno li usa perché SEH / VEH è superiore. Anche su Unix, penso che setjmp e longjmp siano usati molto raramente.
2) Penso che il secondo uso più comune di goto in C sia l'implementazione di interruzioni multilivello o continui multilivello, che è anche un caso d'uso abbastanza controverso. Ricorda che Java non consente di andare all'etichetta, ma consente di interrompere l'etichetta o continuare l'etichetta. Secondo http://www.oracle.com/technetwork/java/simple-142616.html , questo è in realtà il caso d'uso più comune di goto in C (90% dicono), ma nella mia esperienza soggettiva, il codice di sistema tende utilizzare gotos per la gestione degli errori più spesso. Forse nel codice scientifico o in cui il sistema operativo offre la gestione delle eccezioni (Windows), le uscite multilivello sono il caso d'uso dominante. In realtà non forniscono alcun dettaglio sul contesto del loro sondaggio.
Modificato per aggiungere: risulta che questi due schemi di utilizzo si trovano nel libro C di Kernighan e Ritchie, intorno a pagina 60 (a seconda dell'edizione). Un'altra cosa da notare è che entrambi i casi d'uso riguardano solo goto in avanti. E si scopre che l'edizione MISRA C 2012 (a differenza dell'edizione 2004) ora consente goto, purché siano solo avanti.