La guida di stile linux fornisce ragioni specifiche per usare quelle goto
che sono in linea con il tuo esempio:
https://www.kernel.org/doc/Documentation/process/coding-style.rst
La logica per l'utilizzo di gotos è:
- le dichiarazioni incondizionate sono più facili da capire e da seguire
- la nidificazione è ridotta
- si evitano errori non aggiornando i singoli punti di uscita quando si apportano modifiche
- salva il lavoro del compilatore per ottimizzare il codice ridondante;)
Disclaimer Non dovrei condividere il mio lavoro. Gli esempi qui sono un po 'inventati, quindi sopportatemi, per favore, abbiate pazienza.
Questo è buono per la gestione della memoria. Di recente ho lavorato su un codice che aveva allocato dinamicamente la memoria (ad esempio un char *
restituito da una funzione). Una funzione che osserva un percorso e accerta se il percorso è valido analizzando i token del percorso:
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
free(var1);
free(var2);
...
free(varN);
return 1;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
free(var1);
free(var2);
...
free(varN);
return 1;
} else {
free(var1);
free(var2);
...
free(varN);
return 0;
}
}
token = strtok(NULL,delim);
}
free(var1);
free(var2);
...
free(varN);
return 1;
Ora per me, il seguente codice è molto più bello e più facile da mantenere se è necessario aggiungere un varNplus1
:
int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
retval = 1;
goto out_free;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
retval = 1;
goto out_free;
} else {
retval = 0;
goto out_free;
}
}
token = strtok(NULL,delim);
}
out_free:
free(var1);
free(var2);
...
free(varN);
return retval;
Ora il codice aveva ogni sorta di altri problemi con esso, vale a dire che N era da qualche parte sopra 10, e la funzione era di oltre 450 linee, con 10 livelli di annidamento in alcuni punti.
Ma ho offerto al mio supervisore di riformattarlo, cosa che ho fatto e ora è un mucchio di funzioni che sono tutte brevi e tutte hanno lo stile linux
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 == NULL ){
retval = 0;
goto out;
}
if( isValid(var1) ){
retval = some_function(var1);
goto out_free;
}
if( isGood(var1) ){
retval = 0;
goto out_free;
}
out_free:
free(var1);
out:
return retval;
}
Se consideriamo l'equivalente senza goto
s:
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 != NULL ){
if( isValid(var1) ){
retval = some_function(var1);
} else {
if( isGood(var1) ){
retval = 0;
}
}
free(var1);
} else {
retval = 0;
}
return retval;
}
Per me, nel primo caso, è ovvio che se la prima funzione ritorna NULL
, siamo fuori di qui e stiamo tornando 0
. Nel secondo caso, devo scorrere verso il basso per vedere che if contiene l'intera funzione. Concesso il primo mi indica questo stilisticamente (il nome " out
") e il secondo lo fa sintatticamente. Il primo è ancora più ovvio.
Inoltre, preferisco di gran lunga avere delle free()
dichiarazioni alla fine di una funzione. Questo in parte perché, nella mia esperienza, le free()
dichiarazioni nel mezzo di funzioni hanno un cattivo odore e mi indicano che dovrei creare una subroutine. In questo caso, ho creato la var1
mia funzione e non potevo free()
farlo in una subroutine, ma è per questo che lo goto out_free
stile, goto out è così pratico.
Penso che i programmatori debbano essere educati credendo che goto
siano cattivi. Quindi, quando sono abbastanza maturi, dovrebbero sfogliare il codice sorgente di Linux e leggere la guida di stile di Linux.
Dovrei aggiungere che uso questo stile in modo molto coerente, ogni funzione ha un int retval
, out_free
un'etichetta e un'etichetta out. A causa della coerenza stilistica, la leggibilità è migliorata.
Bonus: si interrompe e continua
Supponi di avere un ciclo while
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
if( functionC(var1, var2){
count++
continue;
}
...
a bunch of statements
...
count++;
free(var1);
free(var2);
}
Ci sono altre cose che non vanno in questo codice, ma una cosa è l'istruzione continue. Vorrei riscrivere il tutto, ma mi è stato assegnato il compito di modificarlo in piccolo. Mi ci sarebbero voluti giorni per riformattarlo in un modo che mi soddisfacesse, ma il cambiamento effettivo è stato di circa mezza giornata di lavoro. Il problema è che anche se " continue
" dobbiamo ancora liberarci var1
e var2
. Ho dovuto aggiungere un var3
, e mi ha fatto venire voglia di rispecchiare le dichiarazioni free ().
All'epoca ero un tirocinante relativamente nuovo, ma da un po 'di tempo guardavo il codice sorgente di Linux per divertimento, quindi ho chiesto al mio supervisore se potevo usare un'istruzione goto. Ha detto di sì, e ho fatto questo:
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
var3 = newFunction(line,count);
if( functionC(var1, var2){
goto next;
}
...
a bunch of statements
...
next:
count++;
free(var1);
free(var2);
}
Penso che i continui siano nella migliore delle ipotesi ma per me sono come un goto con un'etichetta invisibile. Lo stesso vale per le pause. Preferirei continuare o interrompere, a meno che, come nel caso qui, non ti costringa a rispecchiare le modifiche in più punti.
E dovrei anche aggiungere che questo uso goto next;
e l' next:
etichetta non sono soddisfacenti per me. Sono semplicemente meglio che rispecchiare free()
le count++
affermazioni e quelle .
goto
Hanno quasi sempre torto, ma bisogna sapere quando sono buoni da usare.
Una cosa che non ho discusso è la gestione degli errori che è stata coperta da altre risposte.
Prestazione
Si può guardare all'implementazione di strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
#include <stddef.h>
#include <string.h>
char *
strtok(s, delim)
register char *s;
register const char *delim;
{
register char *spanp;
register int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
Per favore, correggimi se sbaglio, ma credo che l' cont:
etichetta e la goto cont;
dichiarazione siano lì per le prestazioni (sicuramente non rendono il codice più leggibile). Potrebbero essere sostituiti con codice leggibile facendo
while( isDelim(*s++,delim));
per saltare i delimitatori. Ma per essere il più veloci possibile ed evitare chiamate di funzione non necessarie, lo fanno in questo modo.
Ho letto l'articolo di Dijkstra e lo trovo abbastanza esoterico.
google "dichiarazione di dijkstra goto considerata dannosa" perché non ho abbastanza reputazione per pubblicare più di 2 link.
L'ho visto citato come un motivo per non usare goto e leggerlo non ha cambiato nulla per quanto riguarda i miei usi di goto.
Addendum :
Ho escogitato una regola ordinata mentre penso a tutto ciò su continui e interruzioni.
- Se in un ciclo while, hai un continue, allora il corpo del ciclo while dovrebbe essere una funzione e il continue dovrebbe essere un'istruzione return.
- Se in un ciclo while, hai un'istruzione break, allora il ciclo while stesso dovrebbe essere una funzione e l'interruzione dovrebbe diventare un'istruzione return.
- Se hai entrambi, qualcosa potrebbe essere sbagliato.
Non è sempre possibile a causa di problemi di ambito, ma ho scoperto che farlo rende molto più facile ragionare sul mio codice. Avevo notato che ogni volta che un ciclo di tempo aveva una pausa o una continuazione mi dava una brutta sensazione.