GNU/Linux >> Linux Esercitazione >  >> Linux

Perché alcuni programmatori del kernel usano goto invece di semplici cicli while?

Contesto storico: Dovremmo ricordare che Dijkstra ha scritto Goto Considered Harmful nel 1968, quando molti programmatori usavano goto in sostituzione della programmazione strutturata (if , while , for , eccetera.).

Sono passati 44 anni ed è raro trovare questo uso di goto nella natura selvaggia. La programmazione strutturata ha già vinto, molto tempo fa.

Analisi del caso:

Il codice di esempio ha questo aspetto:

    SETUP...
again:
    COMPUTE SOME VALUES...
    if (cmpxchg64(ptr, old_val, val) != old_val)
        goto again;

La versione strutturata ha questo aspetto:

SETUP...
do {
    COMPUTE SOME VALUES...
} while (cmpxchg64(ptr, old_val, val) != old_val);

Quando guardo la versione strutturata, penso subito "è un loop". Quando guardo il goto versione, la considero come una linea retta con un caso "riprova" alla fine.

Il goto versione ha entrambi SETUP e COMPUTE SOME VALUES sulla stessa colonna, il che sottolinea che la maggior parte delle volte il flusso di controllo passa attraverso entrambi. La versione strutturata mette SETUP e COMPUTE SOME VALUES su colonne diverse, il che sottolinea che il controllo può passare attraverso di esse in modo diverso.

La domanda qui è che tipo di enfasi vuoi mettere nel codice? Puoi confrontarlo con goto per la gestione degli errori:

Versione strutturata:

if (do_something() != ERR) {
    if (do_something2() != ERR) {
        if (do_something3() != ERR) {
            if (do_something4() != ERR) {
                ...

Vai alla versione:

if (do_something() == ERR)  // Straight line
    goto error;             // |
if (do_something2() == ERR) // |
    goto error;             // |
if (do_something3() == ERR) // |
    goto error;             // V
if (do_something4() == ERR) // emphasizes normal control flow
    goto error;

Il codice generato è fondamentalmente lo stesso, quindi possiamo pensarlo come un problema tipografico, come l'indentazione.


Ottima domanda, e penso che solo l'autore (i) possa fornire una risposta definitiva. Aggiungerò la mia piccola speculazione dicendo che avrebbe potuto iniziare usandolo per la gestione degli errori, come spiegato da @Izkata e le porte erano quindi aperte per usarlo anche per i loop di base.

L'utilizzo della gestione degli errori è legittimo nella programmazione dei sistemi, secondo me. Una funzione alloca gradualmente la memoria man mano che procede e, se si verifica un errore, goto l'etichetta appropriata per liberare le risorse in ordine inverso da quel punto.

Quindi, se l'errore si verifica dopo la prima allocazione, passerà all'ultima etichetta di errore, per liberare solo una risorsa. Allo stesso modo, se l'errore si verifica dopo l'ultima allocazione, passerà alla prima etichetta di errore e verrà eseguito da lì, liberando tutte le risorse fino alla fine della funzione. Tale modello per la gestione degli errori deve ancora essere utilizzato con attenzione, specialmente quando si consiglia di modificare il codice, valgrind e unit test. Ma è probabilmente più leggibile e gestibile rispetto agli approcci alternativi.

Una regola d'oro per usare goto è quello di evitare il cosiddetto codice spaghetti. Prova a tracciare linee tra ogni goto dichiarazione e la rispettiva etichetta. Se hai attraversato le linee, beh, hai attraversato una linea :). Tale uso di goto è molto difficile da leggere e una fonte comune di bug difficili da rintracciare, poiché si troverebbero in linguaggi come BASIC che si basavano su di esso per il controllo del flusso.

Se fai solo un semplice loop non otterrai linee incrociate, quindi è ancora leggibile e gestibile, diventando in gran parte una questione di stile. Detto questo, poiché possono essere eseguiti altrettanto facilmente con le parole chiave del ciclo fornite dalla lingua, come hai indicato nella tua domanda, la mia raccomandazione sarebbe comunque quella di evitare di utilizzare goto for loop, semplicemente perché il for , do/while o while i costrutti sono più eleganti per design.


Nel caso di questo esempio, sospetto che si trattasse di adattare il supporto SMP a un codice originariamente scritto in modo non sicuro per SMP. Aggiunta di un goto again; path è molto più semplice e meno invasivo rispetto alla ristrutturazione della funzione.

Non posso dire che mi piaccia molto questo stile, ma penso anche che sia fuorviante evitare goto per ragioni ideologiche. Un caso speciale di goto usage (diverso da questo esempio) è dove goto viene utilizzato solo per andare avanti in una funzione, mai indietro. Questa classe di usi non risulta mai in costrutti di loop derivanti da goto ed è quasi sempre il modo più semplice e chiaro per implementare il comportamento necessario (che di solito consiste nel ripulire e restituire in caso di errore).


Linux
  1. Usa i timer di sistema invece di cronjobs

  2. Errore "mappa in uso" durante la rimozione del dispositivo multipath in CentOS/RHEL

  3. Perché alcuni programmatori del kernel usano goto invece di semplici cicli while?

  4. Perché eval dovrebbe essere evitato in Bash e cosa dovrei usare invece?

  5. Perché usare shm_open?

Perché uso exa invece di ls su Linux

Perché uso rxvt come terminale

Come utilizzare i loop in Ansible Playbook

I 10 motivi principali per utilizzare Linux

Perché i nerd usano Linux

Linux vs Mac OS:15 motivi per utilizzare Linux invece di Mac OS