La guía de estilo de Linux proporciona razones específicas para usar goto
las que están en línea con su ejemplo:
https://www.kernel.org/doc/Documentation/process/coding-style.rst
La razón para usar gotos es:
- las declaraciones incondicionales son más fáciles de entender y seguir
- la anidación se reduce
- errores al no actualizar los puntos de salida individuales cuando se evitan las modificaciones
- guarda el trabajo del compilador para optimizar el código redundante de distancia;)
Descargo de responsabilidad Se supone que no debo compartir mi trabajo. Los ejemplos aquí son un poco artificiales, así que tengan paciencia, tengan paciencia conmigo.
Esto es bueno para la gestión de la memoria. Recientemente trabajé en código que tenía memoria asignada dinámicamente (por ejemplo, un char *
devuelto por una función). Una función que analiza una ruta y determina si la ruta es válida analizando los tokens de la ruta:
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;
Ahora para mí, el siguiente código es mucho mejor y más fácil de mantener si necesita agregar 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;
Ahora el código tenía todo tipo de otros problemas, a saber, que N estaba en algún lugar por encima de 10, y la función tenía más de 450 líneas, con 10 niveles de anidamiento en algunos lugares.
Pero le ofrecí a mi supervisor que lo refactorizara, lo cual hice y ahora es un montón de funciones que son todas cortas, y todas tienen el estilo 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;
}
Si consideramos el equivalente sin 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;
}
Para mí, en el primer caso, es obvio para mí que si la primera función regresa NULL
, nos vamos de aquí y volvemos 0
. En el segundo caso, tengo que desplazarme hacia abajo para ver si el if contiene toda la función. De acuerdo, el primero me indica esto estilísticamente (el nombre " out
") y el segundo lo hace sintácticamente. El primero es aún más obvio.
Además, prefiero tener free()
declaraciones al final de una función. Esto se debe en parte a que, en mi experiencia, las free()
declaraciones en el medio de las funciones huelen mal y me indican que debería crear una subrutina. En este caso, creé var1
en mi función y no pude free()
hacerlo en una subrutina, pero es por eso que el goto out_free
estilo de goto out es tan práctico.
Creo que los programadores necesitan ser educados creyendo que goto
son malos. Luego, cuando sean lo suficientemente maduros, deberían explorar el código fuente de Linux y leer la guía de estilo de Linux.
Debo agregar que uso este estilo de manera muy consistente, cada función tiene retval
una out_free
etiqueta int , una etiqueta y una etiqueta de salida. Debido a la consistencia estilística, se mejora la legibilidad.
Bonus: se rompe y continúa
Digamos que tienes 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);
}
Hay otras cosas mal con este código, pero una cosa es la declaración de continuación. Me gustaría reescribir todo el asunto, pero me encargaron modificarlo de una manera pequeña. Me habría llevado días refactorizarlo de una manera que me satisficiera, pero el cambio real fue aproximadamente medio día de trabajo. El problema es que incluso si nosotros ' continue
' todavía necesitamos liberarnos var1
y var2
. Tuve que agregar un var3
, y me hizo querer vomitar tener que reflejar las declaraciones free ().
Era un pasante relativamente nuevo en ese momento, pero había estado mirando el código fuente de Linux por diversión hace un tiempo, así que le pregunté a mi supervisor si podía usar una declaración goto. Él dijo que sí, e hice esto:
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);
}
Creo que las continuas están bien en el mejor de los casos, pero para mí son como un goto con una etiqueta invisible. Lo mismo ocurre con los descansos. Todavía preferiría continuar o interrumpir a menos que, como fue el caso aquí, te obligue a reflejar modificaciones en varios lugares.
Y también debo agregar que este uso goto next;
y la next:
etiqueta no me satisfacen. Son simplemente mejores que reflejar los free()
'sy las count++
declaraciones.
goto
Casi siempre están equivocados, pero uno debe saber cuándo son buenos para usar.
Una cosa que no discutí es el manejo de errores que ha sido cubierto por otras respuestas.
Actuación
Se puede ver la implementación de 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 */
}
Corríjame si me equivoco, pero creo que la cont:
etiqueta y la goto cont;
declaración están ahí para el rendimiento (seguramente no hacen que el código sea más legible). Podrían ser reemplazados con código legible haciendo
while( isDelim(*s++,delim));
omitir delimitadores. Pero para ser lo más rápido posible y evitar llamadas a funciones innecesarias, lo hacen de esta manera.
Leí el periódico de Dijkstra y lo encuentro bastante esotérico.
google "declaración de dijkstra goto considerada dañina" porque no tengo suficiente reputación para publicar más de 2 enlaces.
Lo he visto citado como una razón para no usar goto's y leerlo no ha cambiado nada en lo que respecta a mis usos de goto's.
Anexo :
Se me ocurrió una regla clara mientras pensaba en todo esto sobre continuos y descansos.
- Si en un ciclo while, tiene una continuación, entonces el cuerpo del ciclo while debe ser una función y la continuación debe ser una declaración de retorno.
- Si en un ciclo while, tiene una instrucción break, entonces el ciclo while en sí mismo debería ser una función y el break debería convertirse en una declaración return.
- Si tiene ambos, entonces algo podría estar mal.
No siempre es posible debido a problemas de alcance, pero he descubierto que hacer esto hace que sea mucho más fácil razonar sobre mi código. Me di cuenta de que cada vez que un ciclo while se interrumpía o continuaba, me daba un mal presentimiento.