Domanda

Tutti sono a conoscenza di Dijkstra Lettera all'editore:vai all'affermazione considerata dannosa (Anche Qui .html trascrizione e Qui .pdf) e da allora c'è stata una formidabile spinta a evitare l'istruzione goto quando possibile.Sebbene sia possibile utilizzare goto per produrre codice esteso e non manutenibile, rimane comunque tale moderni linguaggi di programmazione.Anche quelli avanzati continuazione la struttura di controllo in Scheme può essere descritta come un sofisticato goto.

Quali circostanze giustificano l'uso di goto?Quando è meglio evitare?

Come domanda successiva:C fornisce una coppia di funzioni, setjmp e longjmp, che forniscono la possibilità di andare non solo all'interno dello stack frame corrente ma all'interno di qualsiasi frame chiamante.Dovrebbero essere considerati pericolosi quanto goto?Più pericoloso?


Lo stesso Dijkstra si è pentito di quel titolo, di cui non era responsabile.Alla fine di EWD1308 (Anche Qui .pdf) ha scritto:

Finalmente una breve storia per la cronaca.Nel 1968, le comunicazioni dell'ACM pubblicarono un mio testo sotto il titolo "La dichiarazione Goto considerata dannosa", Che negli anni successivi sarebbe stato più frequentemente citato, purtroppo, tuttavia, spesso da autori che non ne avevano visto più del suo titolo, che divenne una pietra miliare della mia fama diventando un modello:Vedremmo tutti i tipi di articoli con il titolo "X considerato dannoso" per quasi tutti i X, incluso uno intitolato "Dijkstra considerato dannoso".Ma cosa era successo?Avevo presentato un documento con il titolo "Un caso contro la dichiarazione Goto", Che, per accelerare la sua pubblicazione, l'editore si era trasformato in una" lettera all'editore ", e nel frattempo gli aveva dato un nuovo titolo della sua stessa invenzione!L'editore era Niklaus Wirth.

Un classico articolo ben pensato su questo argomento, da abbinare a quello di Dijkstra, lo è Programmazione strutturata con istruzioni go to, di Donald E.Knut.La lettura di entrambi aiuta a ristabilire il contesto e una comprensione non dogmatica dell'argomento.In questo articolo viene riportata l'opinione di Dijkstra su questo caso, che è ancora più forte:

Donald E.Knuth: Credo che presentando tale visione non sono in realtà in disaccordo bruscamente con le idee di Dijkstra, da quando ha recentemente scritto quanto segue:"Per favore, non cadere nella trappola del credere che sono terribilmente dogmatico nei confronti di [la dichiarazione di andare alla dichiarazione]. Ho la sensazione scomoda che gli altri ne stiano facendo una religione, come se i problemi concettuali della programmazione potessero essere risolti da un singolo trucco, da una semplice forma di disciplina codificante!"

È stato utile?

Soluzione

Le seguenti affermazioni sono generalizzazioni;sebbene sia sempre possibile invocare un'eccezione, di solito (secondo la mia esperienza e la mia modesta opinione) non vale i rischi.

  1. L'uso non vincolato degli indirizzi di memoria (GOTO o puntatori grezzi) offre troppe opportunità per commettere errori facilmente evitabili.
  2. Quanti più modi ci sono per arrivare a una particolare "posizione" nel codice, tanto meno si può essere sicuri di quale sia lo stato del sistema in quel punto.(Vedi sotto.)
  3. La programmazione strutturata IMHO riguarda meno "evitare GOTO" e più fare in modo che la struttura del codice corrisponda alla struttura dei dati.Ad esempio, una struttura dati ripetuta (ad es.array, file sequenziale, ecc.) viene naturalmente elaborato da un'unità di codice ripetuta.Avere strutture integrate (ad es.while, for, Until, for-each, ecc.) consente al programmatore di evitare la noia di ripetere gli stessi schemi di codice cliché.
  4. Anche se GOTO è un dettaglio di implementazione di basso livello (non sempre è così!), è al di sotto del livello a cui il programmatore dovrebbe pensare.Quanti programmatori bilanciano i loro libretti degli assegni personali in binario grezzo?Quanti programmatori si preoccupano di quale settore del disco contiene un particolare record, invece di fornire semplicemente una chiave a un motore di database (e in quanti modi le cose potrebbero andare storte se scrivessimo davvero tutti i nostri programmi in termini di settori del disco fisico)?

Note a quanto sopra:

Per quanto riguarda il punto 2, si consideri il seguente codice:

a = b + 1
/* do something with a */

Al punto "fai qualcosa" nel codice, possiamo affermarlo con grande sicurezza a è più grande di b.(Sì, sto ignorando la possibilità di overflow di numeri interi non bloccati.Non impantaniamoci in un semplice esempio.)

D'altra parte, se il codice fosse stato letto in questo modo:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

La molteplicità dei modi per arrivare all’etichetta 10 significa che dobbiamo lavorare molto più duramente per avere fiducia nelle relazioni tra di loro a E b a quel punto.(Infatti nel caso generale è indecidibile!)

Per quanto riguarda il punto 4, l'intera nozione di "andare da qualche parte" nel codice è solo una metafora.Nulla "va" realmente da nessuna parte all'interno della CPU tranne elettroni e fotoni (per il calore disperso).A volte rinunciamo a una metafora per sostituirne un'altra, più utile.Ricordo di aver incontrato (qualche decennio fa!) una lingua dove

if (some condition) {
  action-1
} else {
  action-2
}

è stato implementato su una macchina virtuale compilando action-1 e action-2 come routine senza parametri out-of-line, quindi utilizzando un singolo codice operativo VM a due argomenti che utilizzava il valore booleano della condizione per invocare l'uno o l'altro.Il concetto era semplicemente "scegli cosa invocare adesso" piuttosto che "vai qui o vai là".Ancora una volta, solo un cambio di metafora.

Altri suggerimenti

XKCD's GOTO Comic

Un mio collega ha detto che l'unica ragione per usare un GOTO è se ti sei programmato così lontano in un angolo che è l'unica via d'uscita.In altre parole, una corretta progettazione in anticipo e non sarà necessario utilizzare un GOTO in seguito.

Pensavo che questo fumetto illustri che magnificamente "potevo ristrutturare il flusso del programma o usare un piccolo" goto "invece." Un Goto è una via d'uscita debole quando hai un design debole. I velociraptor predano i deboli.

A volte è valido utilizzare GOTO come alternativa alla gestione delle eccezioni all'interno di una singola funzione:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

Il codice COM sembra rientrare in questo schema abbastanza spesso.

Ricordo di aver usato un goto solo una volta.Avevo una serie di cinque cicli contati annidati e dovevo essere in grado di uscire dall'intera struttura dall'interno in anticipo in base a determinate condizioni:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

Avrei potuto facilmente dichiarare una variabile booleana break e usarla come parte del condizionale per ogni ciclo, ma in questo caso ho deciso che un GOTO era altrettanto pratico e altrettanto leggibile.

Nessun velociraptor mi ha attaccato.

Lo avevamo già discussione e resto a guardare il mio punto.

Inoltre, sono stufo che le persone descrivano le strutture linguistiche di livello superiore come “goto sotto mentite spoglie” perché chiaramente non hanno capito il punto affatto.Per esempio:

Anche la struttura avanzata di controllo della continuazione di Scheme può essere descritta come un sofisticato goto.

Questa è una totale assurdità. Ogni la struttura di controllo può essere implementata in termini di goto ma questa osservazione è del tutto banale e inutile. goto non è considerato dannoso per i suoi effetti positivi ma per le sue conseguenze negative che sono state eliminate mediante una programmazione strutturata.

Allo stesso modo, dire “GOTO è uno strumento e, come tutti gli strumenti, può essere utilizzato e abusato” è completamente fuori luogo.Nessun operaio edile moderno userebbe una roccia e lo affermerebbe "è uno strumento". Le rocce sono state sostituite da martelli. goto è stato sostituito da strutture di controllo.Se l’operaio edile si trovasse in mezzo alla natura senza martello, ovviamente utilizzerebbe una roccia.Se un programmatore deve usare un linguaggio di programmazione inferiore che non ha la funzione X, beh, ovviamente potrebbe dover usare goto Invece.Ma se lo usa altrove invece della caratteristica linguistica appropriata, chiaramente non ha capito bene la lingua e la usa in modo sbagliato.È davvero così semplice.

Goto è estremamente in basso nella mia lista di cose da includere in un programma solo per il gusto di farlo.Ciò non significa che sia inaccettabile.

Goto può essere utile per le macchine statali.Un'istruzione switch in un ciclo è (in ordine di importanza tipica):(a) non effettivamente rappresentativo del flusso di controllo, (b) brutto, (c) potenzialmente inefficiente a seconda del linguaggio e del compilatore.Quindi finisci per scrivere una funzione per stato e fare cose come "return next_state;" che sembrano anche goto.

Certo, è difficile codificare le macchine a stati in modo da renderle facilmente comprensibili.Tuttavia, nessuna di queste difficoltà ha a che fare con l'uso di goto, e nessuna di queste può essere ridotta utilizzando strutture di controllo alternative.A meno che la tua lingua non abbia un costrutto di “macchina statale”.Il mio no.

In quelle rare occasioni in cui il tuo algoritmo è davvero più comprensibile in termini di un percorso attraverso una sequenza di nodi (stati) collegati da un insieme limitato di transizioni consentite (goto), piuttosto che da qualsiasi flusso di controllo più specifico (loop, condizionali, quant'altro). ), allora ciò dovrebbe essere esplicito nel codice.E dovresti disegnare un bel diagramma.

setjmp/longjmp può essere utile per implementare eccezioni o comportamenti simili alle eccezioni.Sebbene non siano universalmente apprezzate, le eccezioni sono generalmente considerate una struttura di controllo "valida".

setjmp/longjmp sono "più pericolosi" di goto nel senso che sono più difficili da usare correttamente, non importa in modo comprensibile.

Non c'è mai stato, né ci sarà mai, nessuna lingua in cui è meno difficile scrivere un codice cattivo.--Donald Knuth.

Togliere goto da C non renderebbe più semplice scrivere un buon codice in C.In effetti, preferirebbe non cogliere il punto che C è ipotetico essere in grado di agire come un glorificato linguaggio assembler.

Successivamente ci sarà "puntatori considerati dannosi", quindi "digitazione papera considerata dannosa".Allora chi rimarrà a difenderti quando verranno a portarti via il tuo costrutto di programmazione non sicuro?Eh?

In Linux:Utilizzo di goto nel codice kernel su Kernel Trap c'è una discussione con Linus Torvalds e un "nuovo ragazzo" sull'uso dei GOTO nel codice Linux.Ci sono degli ottimi punti lì e Linus si è vestito con la solita arroganza :)

Alcuni passaggi:

Linus:"No, sei stato fatto il lavaggio del cervello da persone CS che pensavano che Niklaus Wirth sapesse davvero di cosa stesse parlando.Non l'ha fatto.Non ha un indizio. "

-

Linus:"Penso che i goto vadano bene e sono spesso più leggibili delle grandi quantità di rientranza."

-

Linus:"Certo, in stupide lingue come Pascal, dove le etichette non possono essere descrittive, Goto può essere male."

In C, goto funziona solo nell'ambito della funzione corrente, che tende a localizzare eventuali bug. setjmp E longjmp sono molto più pericolosi, essendo non locali, complicati e dipendenti dall'implementazione.In pratica, tuttavia, sono troppo oscuri e rari per causare molti problemi.

Credo che il pericolo di goto in C è molto esagerato.Ricorda che l'originale goto le discussioni avevano luogo ai tempi dei linguaggi come il vecchio BASIC, dove i principianti scrivevano spaghetti code in questo modo:

3420 IF A > 2 THEN GOTO 1430

Qui Linus descrive un uso appropriato di goto: http://www.kernel.org/doc/Documentation/CodingStyle (capitolo 7).

Oggi è difficile vedere il grosso problema del GOTO perché a vincere il dibattito sono stati soprattutto quelli della "programmazione strutturata" e i linguaggi odierni hanno strutture di flusso di controllo sufficienti per evitarlo GOTO.

Contare il numero di gotos in un moderno programma C.Ora aggiungi il numero di break, continue, E return dichiarazioni.Inoltre, aggiungi il numero di volte in cui lo utilizzi if, else, while, switch O case.Questo è più o meno il numero GOTOcome avrebbe avuto il tuo programma se avessi scritto in FORTRAN o BASIC nel 1968 quando Dijkstra scrisse la sua lettera.

I linguaggi di programmazione all’epoca mancavano di flusso di controllo.Ad esempio, nell'originale Dartmouth BASIC:

  • IF le dichiarazioni non avevano ELSE.Se ne volevi uno, dovevi scrivere:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Anche se il tuo IF la dichiarazione non aveva bisogno di un ELSE, era ancora limitato a una singola riga, che di solito consisteva in a GOTO.

  • Non c'era DO...LOOP dichiarazione.Per non-FOR loops, dovevi terminare il ciclo con un esplicitamente GOTO O IF...GOTO indietro all'inizio.

  • Non c'era SELECT CASE.Dovevi usare ON...GOTO.

Quindi, ti sei ritrovato con a quantità Di GOTOè nel tuo programma.E non potevi dipendere dalla restrizione di GOTOs all'interno di una singola subroutine (perché GOSUB...RETURN era un concetto così debole di subroutine), quindi questi GOTOpotrebbe andare ovunque.Ovviamente, ciò rendeva difficile seguire il flusso di controllo.

È qui che scatta l'anti-GOTO da dove proveniva il movimento.

Go To può fornire una sorta di sostituto per la gestione "reale" delle eccezioni in alcuni casi.Prendere in considerazione:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

Ovviamente questo codice è stato semplificato per occupare meno spazio, quindi non soffermarti troppo sui dettagli.Ma considera un'alternativa che ho visto troppe volte produzione codice da parte dei programmatori che fanno di tutto per evitare di usare goto:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

Ora, funzionalmente, questo codice fa esattamente la stessa cosa.In effetti, il codice generato dal compilatore è quasi identico.Tuttavia, nello zelo del programmatore di placare Nogoto (il temuto dio del rimprovero accademico), questo programmatore ha completamente rotto l'idioma sottostante che il while loop rappresenta, e ha fatto un numero reale sulla leggibilità del codice. Questo non è meglio.

Quindi, la morale della storia è che, se ti ritrovi a ricorrere a qualcosa di veramente stupido per evitare di usare goto, allora non farlo.

Donald E.Knuth ha risposto a questa domanda nel libro "Literate Programming", 1992 CSLI.A pag.17 c'è un saggio "Programmazione strutturata con istruzioni goto" (PDF).Penso che l'articolo potrebbe essere stato pubblicato anche in altri libri.

L'articolo descrive il suggerimento di Dijkstra e descrive le circostanze in cui ciò è valido.Ma fornisce anche una serie di controesempi (problemi e algoritmi) che non possono essere facilmente riprodotti utilizzando solo cicli strutturati.

L'articolo contiene una descrizione completa del problema, la storia, esempi e controesempi.

Attratto dall'aggiunta di una risposta da parte di Jay Ballou, aggiungerò i miei £ 0,02.Se Bruno Ranschaert non lo avesse già fatto, avrei menzionato l'articolo di Knuth "Programmazione strutturata con istruzioni GOTO".

Una cosa di cui non ho visto discutere è il tipo di codice che, sebbene non esattamente comune, veniva insegnato nei libri di testo Fortran.Cose come la gamma estesa di un ciclo DO e subroutine a codice aperto (ricordate, questo sarebbe Fortran II, o Fortran IV, o Fortran 66 - non Fortran 77 o 90).C'è almeno la possibilità che i dettagli sintattici siano inesatti, ma i concetti dovrebbero essere sufficientemente accurati.Gli snippet in ogni caso si trovano all'interno di una singola funzione.

Da notare che il libro eccellente ma datato (e fuori stampa) 'Gli elementi dello stile di programmazione, 2a ed' di Kernighan & Plauger include alcuni esempi di vita reale di abuso di GOTO da parte di libri di testo di programmazione della sua epoca (fine anni '70).Tuttavia, il materiale riportato di seguito non proviene da quel libro.

Intervallo esteso per un ciclo DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

Uno dei motivi di queste sciocchezze era la buona vecchia scheda perforata.Potresti notare che le etichette (ben fuori sequenza perché era in stile canonico!) sono nella colonna 1 (in realtà, dovevano essere nelle colonne 1-5) e il codice è nelle colonne 7-72 ​​(la colonna 6 era la continuazione colonna dei marcatori).Alle colonne 73-80 veniva assegnato un numero di sequenza e c'erano macchine che ordinavano i mazzi di carte perforate in ordine di numero di sequenza.Se avessi il tuo programma su carte in sequenza e avessi bisogno di aggiungere alcune carte (righe) nel mezzo di un ciclo, dovresti ricomporre tutto dopo quelle righe extra.Tuttavia, se sostituissi una carta con il materiale GOTO, potresti evitare di riordinare tutte le carte: hai semplicemente nascosto le nuove carte alla fine della routine con i nuovi numeri di sequenza.Consideratelo il primo tentativo di "informatica verde" - un risparmio di schede perforate (o, più specificamente, un risparmio di manodopera di ribattitura - e un risparmio di conseguenti errori di riscrittura).

Oh, potresti anche notare che sto barando e non gridando: Fortran IV è stato scritto normalmente in maiuscolo.

Subroutine a codice aperto

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

Il GOTO tra le etichette 76 e 54 è una versione del goto calcolato.Se la variabile i ha valore 1, vai alla prima etichetta della lista (123);se ha valore 2, vai al secondo e così via.Il frammento da 76 al goto calcolato è la subroutine a codice aperto.Era un pezzo di codice eseguito piuttosto come una subroutine, ma scritto nel corpo di una funzione.(Fortran aveva anche funzioni di istruzione, che erano funzioni incorporate che si adattavano a un'unica riga.)

C'erano costrutti peggiori del goto calcolato: potevi assegnare etichette alle variabili e quindi utilizzare un goto assegnato.Cercando su Google assegnato vai a mi dice che è stato cancellato da Fortran 95.Iniziamo con la rivoluzione della programmazione strutturata che si potrebbe giustamente dire che sia iniziata in pubblico con la lettera o l'articolo di Dijkstra "GOTO Considered Harmful".

Senza una certa conoscenza del genere di cose che venivano fatte in Fortran (e in altre lingue, la maggior parte delle quali sono giustamente cadute nel dimenticatoio), è difficile per noi nuovi arrivati ​​comprendere la portata del problema con cui Dijkstra si stava occupando.Diamine, non ho iniziato a programmare fino a dieci anni dopo la pubblicazione di quella lettera (ma ho avuto la sfortuna di programmare in Fortran IV per un po').

Vai a considerato utile.

Ho iniziato a programmare nel 1975.Per i programmatori degli anni '70, le parole "vai considerato dannoso" dicevano più o meno che valeva la pena provare nuovi linguaggi di programmazione con moderne strutture di controllo.Abbiamo provato le nuove lingue.Ci siamo convertiti rapidamente.Non siamo mai tornati indietro.

Non siamo mai tornati indietro, ma se sei più giovane significa che non ci sei mai stato.

Ora, una conoscenza degli antichi linguaggi di programmazione potrebbe non essere molto utile se non come indicatore dell'età del programmatore.Tuttavia, i programmatori più giovani non hanno questo background, quindi non capiscono più il messaggio trasmesso dallo slogan "vai a considerarlo dannoso". al pubblico previsto al momento della sua presentazione.

Gli slogan che non si comprendono non sono molto illuminanti.Probabilmente è meglio dimenticare questi slogan.Tali slogan non aiutano.

Questo particolare slogan, tuttavia, "Goto considerato dannoso", ha assunto una propria vita non morta.

Non è possibile abusare del goto?Risposta:certo, ma allora?Praticamente ogni elemento di programmazione Potere essere abusato.Gli umili bool ad esempio, viene abusato più spesso di quanto alcuni di noi vorrebbero credere.

Al contrario, non ricordo di aver incontrato un singolo caso reale di abuso dal 1990.

Il problema più grande con goto probabilmente non è tecnico ma sociale.I programmatori che non sanno molto a volte sembrano pensare che deprecare goto li faccia sembrare intelligenti.Di tanto in tanto potresti dover soddisfare tali programmatori.Così è la vita.

La cosa peggiore del goto oggi è che non viene utilizzato abbastanza.

Non esistono cose come GOTO considerato dannoso.

GOTO è uno strumento e, come tutti gli strumenti, può essere utilizzato e abusato.

Esistono, tuttavia, molti strumenti nel mondo della programmazione che tendono ad esserlo abusato più che essere usato, e GOTO è uno di questi.IL CON l'affermazione di Delfi è un'altra.

Personalmente non uso nessuno dei due nel codice tipico, ma ho avuto lo strano utilizzo di entrambi VAI A E CON ciò era garantito e una soluzione alternativa avrebbe contenuto più codice.

La soluzione migliore sarebbe che il compilatore ti avvisasse semplicemente che la parola chiave era contaminato, e dovresti inserire un paio di direttive pragma attorno all'istruzione per eliminare gli avvertimenti.

È come dirlo ai tuoi figli non correre con le forbici.Le forbici non sono male, ma il loro utilizzo forse non è il modo migliore per mantenere la salute.

Da quando ho iniziato a fare alcune cose nel kernel Linux, i goto non mi danno più fastidio come una volta.All'inizio ero un po' inorridito nel vedere che loro (quelli del kernel) avevano aggiunto dei goto al mio codice.Da allora mi sono abituato all'uso dei goto, in alcuni contesti limitati, e ora li userò io stesso occasionalmente.In genere, è un goto che salta alla fine di una funzione per eseguire una sorta di pulizia e salvataggio, anziché duplicare la stessa pulizia e salvataggio in diversi punti della funzione.E in genere, non è qualcosa di abbastanza grande da poter essere trasferito a un'altra funzione, ad es.la liberazione di alcune variabili locali (k)malloc è un caso tipico.

Ho scritto codice che utilizzava setjmp/longjmp solo una volta.Era in un programma di sequenziamento di batteria MIDI.La riproduzione è avvenuta in un processo separato da tutte le interazioni dell'utente e il processo di riproduzione ha utilizzato la memoria condivisa con il processo dell'interfaccia utente per ottenere le informazioni limitate necessarie per eseguire la riproduzione.Quando l'utente voleva interrompere la riproduzione, il processo di riproduzione eseguiva semplicemente un lungo jmp "torna all'inizio" per ricominciare, piuttosto che un complicato svolgimento del punto in cui si trovava in esecuzione quando l'utente voleva che si interrompesse.Ha funzionato alla grande, è stato semplice e in quel caso non ho mai avuto problemi o bug ad esso correlati.

setjmp/longjmp hanno il loro posto, ma quel posto è uno che probabilmente non visiterai se non una volta ogni tanto.

Modificare:Ho appena guardato il codice.In realtà è stato siglongjmp() quello che ho usato, non longjmp (non che sia un grosso problema, ma avevo dimenticato che siglongjmp esistesse).

Non lo è mai stato, finché eri in grado di pensare con la tua testa.

Se stai scrivendo una VM in C, risulta che utilizzando i goto calcolati (di gcc) in questo modo:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

funziona molto più velocemente dello switch convenzionale all'interno di un loop.

Perché goto può essere usato per confondere la metaprogrammazione

Goto è sia a alto livello e un basso livello control e di conseguenza semplicemente non ha un modello di progettazione appropriato adatto alla maggior parte dei problemi.

Suo basso livello nel senso che goto è un'operazione primitiva che implementa qualcosa di più elevato come while O foreach o qualcosa.

Suo alto livello nel senso che quando viene utilizzato in un certo modo prende codice che viene eseguito in una sequenza chiara, in modo ininterrotto, tranne che per i cicli strutturati, e lo trasforma in pezzi di logica che sono, con sufficiente gotos, un insieme di logica che viene riassemblato dinamicamente.

Quindi, c'è un prosaico e un cattivo lato a goto.

IL lato prosaico è che un goto che punta verso l'alto può implementare un ciclo perfettamente ragionevole e un goto che punta verso il basso può fare un ciclo perfettamente ragionevole break O return.Naturalmente, un vero e proprio while, break, O return sarebbe molto più leggibile, poiché il povero essere umano non dovrebbe simulare l'effetto di goto per avere il quadro generale.Quindi, una cattiva idea in generale.

IL lato malvagio implica una routine che non usa goto per while, break o return, ma la usa per quello che viene chiamato logica degli spaghetti.In questo caso lo sviluppatore felice di goto sta costruendo pezzi di codice da un labirinto di goto, e l'unico modo per capirlo è simularlo mentalmente nel suo insieme, un compito terribilmente faticoso quando ci sono molti goto.Voglio dire, immagina il problema di valutare il codice in cui il file else non è esattamente l'inverso di if, dove nidificato ifPotrebbero consentire alcune cose che sono state rifiutate dall'esterno if, ecc, ecc.

Infine, per coprire realmente l'argomento, dovremmo notare che essenzialmente tutte le lingue antiche, tranne l'Algol, inizialmente facevano solo singole affermazioni soggette alle loro versioni di if-then-else.Quindi, l'unico modo per eseguire un blocco condizionale era goto attorno ad esso utilizzando un condizionale inverso.Pazzesco, lo so, ma ho letto alcune vecchie specifiche.Ricorda che i primi computer erano programmati in codice macchina binario, quindi suppongo che qualsiasi tipo di HLL fosse un vero toccasana;Immagino che non fossero troppo esigenti riguardo esattamente alle funzionalità HLL che avevano.

Detto questo ne attaccavo uno goto in ogni programma che ho scritto "giusto per dare fastidio ai puristi".

Negare l'uso dell'istruzione GOTO ai programmatori è come dire a un falegname di non usare un martello perché potrebbe danneggiare il muro mentre sta piantando un chiodo.Un vero programmatore sa come e quando utilizzare un GOTO.Ho seguito alcuni di questi cosiddetti "Programmi strutturati" e ho visto un codice così orribile solo per evitare di usare un GOTO, in modo da poter sparare al programmatore.Ok, in difesa dell'altra parte, ho visto anch'io dei veri spaghetti code e, ancora una volta, anche quei programmatori dovrebbero essere fucilati.

Ecco solo un piccolo esempio di codice che ho trovato.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------O----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

"In questo collegamento http://kerneltrap.org/node/553/2131"

Ironicamente, l'eliminazione del goto ha introdotto un bug:la chiamata spinlock è stata omessa.

Il documento originale dovrebbe essere considerato come "GOTO incondizionato considerato dannoso".Si proponeva in particolare una forma di programmazione basata sul condizionale (if) e iterativo (while), piuttosto che il test-and-jump comune al codice iniziale. goto è ancora utile in alcune lingue o circostanze, dove non esiste una struttura di controllo adeguata.

Circa l'unico posto in cui sono d'accordo, Goto Potevo essere utilizzato è quando è necessario gestire gli errori e ogni punto particolare in cui si verifica un errore richiede una gestione speciale.

Ad esempio, se stai raccogliendo risorse e utilizzando semafori o mutex, devi prenderle in ordine e dovresti sempre rilasciarle nel modo opposto.

Alcuni codici richiedono uno schema molto strano per acquisire queste risorse e non è possibile semplicemente scrivere una struttura di controllo facilmente gestibile e comprensibile per gestire correttamente sia l'acquisizione che il rilascio di queste risorse per evitare situazioni di stallo.

È sempre possibile farlo bene senza goto, ma in questo caso e in pochi altri Goto è in realtà la soluzione migliore principalmente per leggibilità e manutenibilità.

-Adamo

Un utilizzo moderno di GOTO è da parte del compilatore C# per creare macchine a stati per enumeratori definiti da yield return.

GOTO è qualcosa che dovrebbe essere utilizzato dai compilatori e non dai programmatori.

Fino a quando C e C++ (tra gli altri colpevoli) non avranno etichettato interruzioni e continui, goto continuerà ad avere un ruolo.

Se lo stesso GOTO fosse malvagio, i compilatori sarebbero malvagi, perché generano JMP.Se saltare in un blocco di codice, soprattutto seguendo un puntatore, fosse intrinsecamente malvagio, l'istruzione RETurn sarebbe malvagia.Piuttosto, il male sta nel potenziale di abuso.

A volte ho dovuto scrivere app che dovevano tenere traccia di un numero di oggetti in cui ogni oggetto doveva seguire un'intricata sequenza di stati in risposta agli eventi, ma il tutto era decisamente a thread singolo.Una tipica sequenza di stati, se rappresentata in pseudo-codice sarebbe:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

Sono sicuro che non sia una novità, ma il modo in cui l'ho gestito in C(++) è stato definire alcune macro:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

Quindi (assumendo che lo stato sia inizialmente 0) la macchina a stati strutturata sopra si trasforma nel codice strutturato:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

Con una variazione su questo, ci possono essere CALL e RETURN, quindi alcune macchine a stati possono agire come subroutine di altre macchine a stati.

È insolito?SÌ.Ci vuole un po' di apprendimento da parte del manutentore?SÌ.Questo apprendimento ripaga?Credo di si.Si potrebbe fare senza GOTO che saltano nei blocchi?No.

Lo evito poiché un collega/manager metterà senza dubbio in dubbio il suo utilizzo in una revisione del codice o quando si imbatte in esso.Anche se penso che abbia degli usi (ad esempio il caso di gestione degli errori), ti imbatterai in qualche altro sviluppatore che avrà qualche tipo di problema con esso.

Non ne vale la pena.

In realtà mi sono trovato costretto a usare un goto, perché letteralmente non riuscivo a pensare a un modo migliore (più veloce) per scrivere questo codice:

Avevo un oggetto complesso e avevo bisogno di eseguire qualche operazione su di esso.Se l'oggetto si trovava in uno stato, avrei potuto eseguire una versione rapida dell'operazione, altrimenti avrei dovuto eseguire una versione lenta dell'operazione.Il fatto è che in alcuni casi, nel bel mezzo dell'operazione lenta, è stato possibile rendersi conto che ciò si sarebbe potuto fare con l'operazione veloce.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

Questo era in un pezzo critico di velocità del codice dell'interfaccia utente in tempo reale, quindi onestamente penso che un GOTO fosse giustificato qui.

Ugo

Quasi tutte le situazioni in cui è possibile utilizzare goto, è possibile fare lo stesso utilizzando altri costrutti.Goto viene comunque utilizzato dal compilatore.

Personalmente non lo uso mai esplicitamente, non ne ho mai bisogno.

Una cosa non l'ho vista Qualunque una delle risposte qui è che spesso è disponibile una soluzione "vai a". più efficiente di una delle soluzioni di programmazione strutturata spesso menzionate.

Considera il caso di molti cicli nidificati, in cui si utilizza 'goto' invece di un gruppo di if(breakVariable) sezioni è ovviamente più efficiente.La soluzione "metti i tuoi loop in una funzione e usa return" è spesso del tutto irragionevole.Nel caso probabile che i cicli utilizzino variabili locali, ora è necessario passarle tutte attraverso i parametri della funzione, gestendo potenzialmente un sacco di grattacapi aggiuntivi che ne derivano.

Consideriamo ora il caso cleanup, che ho usato io stesso abbastanza spesso, ed è così comune da essere presumibilmente responsabile della struttura try{} catch {} non disponibile in molte lingue.Il numero di controlli e di variabili aggiuntive necessarie per ottenere la stessa cosa è di gran lunga peggiore di una o due istruzioni per effettuare il salto e, ancora una volta, la soluzione della funzione aggiuntiva non è affatto una soluzione.Non puoi dirmi che sia più gestibile o più leggibile.

Ora lo spazio del codice, l'utilizzo dello stack e il tempo di esecuzione potrebbero non essere abbastanza importanti in molte situazioni per molti programmatori, ma quando ti trovi in ​​un ambiente incorporato con solo 2 KB di spazio di codice con cui lavorare, 50 byte di istruzioni aggiuntive per evitarne una chiaramente definita 'goto' è semplicemente ridicolo, e questa non è una situazione così rara come credono molti programmatori di alto livello.

L'affermazione che "goto è dannoso" è stata molto utile nel passaggio alla programmazione strutturata, anche se si è sempre trattato di una generalizzazione eccessiva.A questo punto, ne abbiamo tutti sentito parlare abbastanza per diffidare del suo utilizzo (come dovremmo).Quando è ovviamente lo strumento giusto per il lavoro, non dobbiamo averne paura.

Puoi usarlo per interrompere un ciclo profondamente annidato, ma la maggior parte delle volte il tuo codice può essere sottoposto a refactoring per essere più pulito senza cicli profondamente annidati.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top