Respuestas:
Hay algunas razones para usar la declaración "goto" que conozco (algunos ya han hablado de esto):
Salir limpiamente de una función
A menudo, en una función, puede asignar recursos y debe salir en varios lugares. Los programadores pueden simplificar su código colocando el código de limpieza de recursos al final de la función, y todos los "puntos de salida" de la función irían a la etiqueta de limpieza. De esta manera, no tiene que escribir código de limpieza en cada "punto de salida" de la función.
Salir de bucles anidados
Si está en un bucle anidado y necesita salir de todos los bucles, un goto puede hacer esto mucho más limpio y simple que las declaraciones de interrupción y las comprobaciones if.
Mejoras de rendimiento de bajo nivel.
Esto solo es válido en el código crítico, pero las instrucciones goto se ejecutan muy rápidamente y pueden darle un impulso cuando se mueve a través de una función. Sin embargo, esta es una espada de doble filo, porque un compilador generalmente no puede optimizar el código que contiene gotos.
Tenga en cuenta que en todos estos ejemplos, los gotos están restringidos al alcance de una sola función.
goto
con return
es simplemente una tontería. No es "refactorizar" nada, es solo "renombrar" para que las personas que crecieron en un goto
entorno deprimido (es decir, todos nosotros) se sientan mejor al usar lo que moralmente equivale a a goto
. Prefiero ver el bucle donde lo uso y ver un poco goto
, que en sí mismo es solo una herramienta , que ver a alguien que ha movido el bucle a otro lugar no relacionado solo para evitar a goto
.
break
, continue
, return
son, básicamente goto
, por si empaquetado agradable.
do{....}while(0)
se supone que es una mejor idea que goto, excepto por el hecho de que funciona en Java.
Todo el mundo que sea anticuado goto
, directa o indirectamente, el artículo GoTo Considered Damful de Edsger Dijkstra para corroborar su posición. Lástima que el artículo de Dijkstra no tenga prácticamente nada que ver con la forma en goto
que se usan las declaraciones en estos días y, por lo tanto, lo que dice el artículo tiene poca o ninguna aplicabilidad en la escena de la programación moderna. El goto
meme sin filo ahora raya en una religión, hasta sus escrituras dictadas desde lo alto, sus sumos sacerdotes y el rechazo (o peor) de los herejes percibidos.
Pongamos el artículo de Dijkstra en contexto para arrojar un poco de luz sobre el tema.
Cuando Dijkstra escribió su artículo, los idiomas populares de la época eran los de procedimientos no estructurados como BASIC, FORTRAN (los dialectos anteriores) y varios lenguajes de ensamblaje. Era bastante común que las personas que usaban los lenguajes de nivel superior saltaran sobre su base de código en hilos retorcidos y retorcidos de ejecución que dieron lugar al término "código de espagueti". Puedes ver esto yendo al clásico juego de Trek escrito por Mike Mayfield e intentando descubrir cómo funcionan las cosas. Tómese unos minutos para revisar eso.
ESTE es "el uso desenfrenado de la declaración ir a" contra la cual Dijkstra estaba criticando en su artículo en 1968. ESTE es el entorno en el que vivió que lo llevó a escribir ese documento. La capacidad de saltar a cualquier lugar que desee en su código en cualquier momento que le gustaba era lo que estaba criticando y exigiendo que se detuviera. Comparar eso con los poderes anémicos de goto
C u otros lenguajes más modernos es simplemente risible.
Ya puedo escuchar los cantos de los cultistas cuando se enfrentan al hereje. "Pero", gritarán, "puede hacer que el código sea muy difícil de leer goto
en C." ¿Oh si? También puede hacer que el código sea muy difícil de leer sin él goto
. Como éste:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
No está goto
a la vista, por lo que debe ser fácil de leer, ¿verdad? O qué tal este:
a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k,
l)char* *l;{g= atoi(* ++l); for(k=
0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<=
g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1;
while(d <=g){ ++O;for (f=0;f< O&&d<=g
;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O
&&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e
;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++
b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1))
<<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c]
;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5
|c]);} putchar( '\n');}} /*Mike Laman*/
No goto
hay tampoco. Por lo tanto, debe ser legible.
¿Cuál es mi punto con estos ejemplos? No son las características del lenguaje las que hacen que el código sea ilegible e imposible de mantener. No es la sintaxis lo que lo hace. Son los malos programadores los que causan esto. Y los malos programadores, como puede ver en el elemento anterior, pueden hacer que cualquier característica del lenguaje sea ilegible e inutilizable. Como los for
bucles de allá arriba. (Puedes verlos, ¿verdad?)
Ahora, para ser justos, algunas construcciones de lenguaje son más fáciles de abusar que otras. Sin embargo, si eres un programador en C, miraría mucho más de cerca el 50% de los usos de #define
mucho antes de ir a una cruzada en contra goto
.
Entonces, para aquellos que se han molestado en leer hasta aquí, hay varios puntos clave a tener en cuenta.
goto
declaraciones fue escrito para un entorno de programación donde goto
era mucho
más dañino que en la mayoría de los lenguajes modernos que no son ensambladores.goto
esto es tan racional como decir "Intenté divertirme una vez pero no me gustó, así que ahora estoy en contra".goto
declaraciones modernas (anémicas) en el código que no pueden ser reemplazadas adecuadamente por otras construcciones.godo
" abominación en la que do
se interrumpe el uso break
de un bucle siempre falso en lugar de a goto
. Estos a menudo son peores que el uso juicioso de goto
.goto
realidad (que es la pregunta publicada)
Obedecer las mejores prácticas a ciegas no es una buena práctica. La idea de evitar las goto
declaraciones como la forma principal de control de flujo es evitar producir un código de spaghetti ilegible. Si se usan con moderación en los lugares correctos, a veces pueden ser la forma más simple y clara de expresar una idea. Walter Bright, el creador del compilador Zortech C ++ y el lenguaje de programación D, los usa con frecuencia, pero con criterio. Incluso con las goto
declaraciones, su código sigue siendo perfectamente legible.
En pocas palabras: evitar goto
por el simple hecho de evitar goto
es inútil. Lo que realmente quieres evitar es producir código ilegible. Si su goto
código cargado es legible, entonces no tiene nada de malo.
Dado goto
que el razonamiento sobre el flujo del programa es difícil 1 (también conocido como "código de espagueti"), goto
generalmente solo se usa para compensar las características faltantes: el uso de goto
puede ser realmente aceptable, pero solo si el lenguaje no ofrece una variante más estructurada para obtener El mismo objetivo. Tomemos el ejemplo de Doubt:
La regla con goto que usamos es que goto está bien para saltar hacia adelante a un único punto de limpieza de salida en una función.
Esto es cierto, pero solo si el lenguaje no permite el manejo estructurado de excepciones con código de limpieza (como RAII o finally
), que hace el mismo trabajo mejor (ya que está especialmente diseñado para hacerlo), o cuando hay una buena razón para no para emplear el manejo estructurado de excepciones (pero nunca tendrá este caso, excepto en un nivel muy bajo).
En la mayoría de los otros idiomas, el único uso aceptable de goto
es salir de los bucles anidados. E incluso allí, casi siempre es mejor elevar el bucle externo a un método propio y usarlo return
en su lugar.
Aparte de eso, goto
es una señal de que no se ha pensado lo suficiente en el fragmento de código en particular.
1 Los lenguajes modernos que admiten goto
implementar algunas restricciones (por ejemplo, goto
pueden no entrar o salir de las funciones), pero el problema sigue siendo el mismo.
Por cierto, lo mismo es, por supuesto, también cierto para otras características del lenguaje, especialmente las excepciones. Y, por lo general, existen reglas estrictas para usar solo estas características donde se indica, como la regla de no usar excepciones para controlar el flujo de programas no excepcionales.
finally
? Entonces, ¿usar excepciones para otras cosas además del manejo de errores es bueno pero usar goto
es malo? Creo que las excepciones tienen un nombre bastante acertado.
Bueno, hay una cosa que siempre es peor que goto's
; uso extraño de otros operadores de flujo de programa para evitar un goto:
Ejemplos:
// 1
try{
...
throw NoErrorException;
...
} catch (const NoErrorException& noe){
// This is the worst
}
// 2
do {
...break;
...break;
} while (false);
// 3
for(int i = 0;...) {
bool restartOuter = false;
for (int j = 0;...) {
if (...)
restartOuter = true;
if (restartOuter) {
i = -1;
}
}
etc
etc
do{}while(false)
Creo que puede considerarse idiomático. No está permitido estar en desacuerdo: D
goto after_do_block;
sin decirlo. De lo contrario ... ¿un "bucle" que se ejecuta exactamente una vez? Yo llamaría a eso estructuras de abuso de control.
#define
las cosas son muchas, muchas veces mucho peores que goto
En la declaración de cambio de C # no se permite la caída . Por lo tanto, goto se usa para transferir el control a una etiqueta de caja de conmutación específica o la etiqueta predeterminada .
Por ejemplo:
switch(value)
{
case 0:
Console.Writeln("In case 0");
goto case 1;
case 1:
Console.Writeln("In case 1");
goto case 2;
case 2:
Console.Writeln("In case 2");
goto default;
default:
Console.Writeln("In default");
break;
}
Editar: hay una excepción en la regla "no fall-through". Se permite la caída si una declaración de caso no tiene código.
goto case 5:
cuándo estás en el caso 1). Parece que la respuesta de Konrad Rudolph es correcta aquí: goto
está compensando una característica faltante (y es menos clara de lo que sería la característica real). Si lo que realmente queremos es una falla, quizás el mejor valor predeterminado sería no caer, sino algo así como continue
solicitarlo explícitamente.
#ifdef TONGUE_IN_CHEEK
Perl tiene una goto
que le permite implementar llamadas de cola de pobres. :-PAGS
sub factorial {
my ($n, $acc) = (@_, 1);
return $acc if $n < 1;
@_ = ($n - 1, $acc * $n);
goto &factorial;
}
#endif
Bueno, por lo que no tiene nada que ver con C de goto
. Más en serio, estoy de acuerdo con los otros comentarios sobre el uso goto
para limpiezas, o para implementar el dispositivo de Duff , o similares. Se trata de usar, no abusar.
(El mismo comentario puede aplicarse a longjmp
, excepciones call/cc
y similares, tienen usos legítimos, pero pueden ser fácilmente abusados. Por ejemplo, lanzar una excepción puramente para escapar de una estructura de control profundamente anidada, bajo circunstancias completamente no excepcionales .)
He escrito más de unas pocas líneas de lenguaje ensamblador a lo largo de los años. En definitiva, cada lenguaje de alto nivel se compila en gotos. Bien, llámalos "ramas" o "saltos" o lo que sea, pero son gotos. ¿Alguien puede escribir ensamblador sin goto?
Ahora seguro, puede indicarle a un programador de Fortran, C o BASIC que ejecutar disturbios con gotos es una receta de espagueti a la boloñesa. Sin embargo, la respuesta no es evitarlos, sino usarlos con cuidado.
Se puede usar un cuchillo para preparar alimentos, liberar a alguien o matar a alguien. ¿Lo hacemos sin cuchillos por miedo a lo último? Del mismo modo, el goto: usado descuidadamente obstaculiza, usado con cuidado ayuda.
Echa un vistazo a Cuándo usar Goto al programar en C :
Aunque el uso de goto es casi siempre una mala práctica de programación (seguramente puede encontrar una mejor manera de hacer XYZ), hay momentos en los que realmente no es una mala elección. Algunos incluso podrían argumentar que, cuando es útil, es la mejor opción.
La mayor parte de lo que tengo que decir sobre goto realmente solo se aplica a C. Si está usando C ++, no hay una buena razón para usar goto en lugar de excepciones. En C, sin embargo, no tiene el poder de un mecanismo de manejo de excepciones, por lo que si desea separar el manejo de errores del resto de la lógica de su programa, y desea evitar reescribir el código de limpieza varias veces en todo el código, entonces goto puede ser una buena opción.
¿Que quiero decir? Es posible que tenga un código similar a este:
int big_function()
{
/* do some work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* clean up*/
return [success];
}
Esto está bien hasta que te des cuenta de que necesitas cambiar tu código de limpieza. Luego tienes que pasar y hacer 4 cambios. Ahora, puede decidir que puede encapsular toda la limpieza en una sola función; Esa no es una mala idea. Pero sí significa que deberá tener cuidado con los punteros: si planea liberar un puntero en su función de limpieza, no hay forma de configurarlo para que apunte a NULL a menos que pase un puntero a un puntero. En muchos casos, no volverá a utilizar ese puntero de todos modos, por lo que puede que no sea una gran preocupación. Por otro lado, si agrega un nuevo puntero, identificador de archivo u otra cosa que necesita limpieza, entonces deberá cambiar su función de limpieza nuevamente; y luego necesitarás cambiar los argumentos a esa función.
Al usar goto
, será
int big_function()
{
int ret_val = [success];
/* do some work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
end:
/* clean up*/
return ret_val;
}
El beneficio aquí es que su código siguiente tiene acceso a todo lo que necesitará para realizar la limpieza, y ha logrado reducir la cantidad de puntos de cambio considerablemente. Otro beneficio es que ha pasado de tener múltiples puntos de salida para su función a solo uno; no hay posibilidad de que accidentalmente regrese de la función sin limpiar.
Además, dado que goto solo se usa para saltar a un solo punto, no es como si estuvieras creando una masa de código de espagueti saltando de un lado a otro en un intento de simular llamadas a funciones. Más bien, goto realmente ayuda a escribir código más estructurado.
En una palabra, goto
siempre debe usarse con moderación y como último recurso, pero hay un momento y un lugar para ello. La pregunta no debería ser "¿tiene que usarlo" sino "es la mejor opción" para usarlo?
Una de las razones por las que goto es malo, además del estilo de codificación, es que puedes usarlo para crear bucles superpuestos , pero no anidados :
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
Esto crearía la estructura de flujo de control extraña, pero posiblemente legal, donde una secuencia como (a, b, c, b, a, b, a, b, ...) es posible, lo que hace que los piratas informáticos del compilador sean infelices. Aparentemente, hay una serie de ingeniosos trucos de optimización que dependen de que este tipo de estructura no ocurra. (Debería verificar mi copia del libro del dragón ...) El resultado de esto podría (usando algunos compiladores) ser que no se realizan otras optimizaciones para el código que contiene goto
s.
Puede ser útil si lo sabe simplemente, "oh, por cierto", convence al compilador para que emita un código más rápido. Personalmente, preferiría tratar de explicarle al compilador qué es probable y qué no antes de usar un truco como goto, pero podría decirse que también podría intentarlo goto
antes de hackear el ensamblador.
goto
es útil es que le permite construir bucles como este, que de lo contrario requerirían un montón de contorsiones lógicas. Además, argumentaría que si el optimizador no sabe cómo reescribir esto, entonces está bien . Un bucle como este no debe hacerse por rendimiento o legibilidad, sino porque ese es exactamente el orden en que las cosas deben suceder. En cuyo caso, no quisiera que el optimizador lo fastidiara.
Me parece curioso que algunas personas lleguen a dar una lista de casos en los que goto es aceptable, diciendo que todos los demás usos son inaceptables. ¿De verdad crees que conoces todos los casos en que goto es la mejor opción para expresar un algoritmo?
Para ilustrar, te daré un ejemplo que nadie aquí ha mostrado todavía:
Hoy estaba escribiendo código para insertar un elemento en una tabla hash. La tabla hash es un caché de cálculos anteriores que se pueden sobrescribir a voluntad (lo que afecta el rendimiento pero no la corrección).
Cada depósito de la tabla hash tiene 4 espacios, y tengo un montón de criterios para decidir qué elemento sobrescribir cuando un depósito está lleno. En este momento, esto significa hacer hasta tres pases a través de un cubo, así:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
goto add;
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
goto add;
// Additional passes go here...
add:
// element is written to the hash table here
Ahora, si no usara goto, ¿cómo sería este código?
Algo como esto:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
break;
if (add_index >= ELEMENTS_PER_BUCKET) {
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
break;
if (add_index >= ELEMENTS_PER_BUCKET)
// Additional passes go here (nested further)...
}
// element is written to the hash table here
Se vería peor y peor si se agregan más pases, mientras que la versión con goto mantiene el mismo nivel de sangría en todo momento y evita el uso de declaraciones espurias if cuyo resultado está implícito en la ejecución del bucle anterior.
Entonces, hay otro caso en el que goto hace que el código sea más limpio y fácil de escribir y comprender ... Estoy seguro de que hay muchos más, así que no pretendas saber todos los casos en los que goto es útil, descartando los buenos que no puedas No pienses en eso.
goto
que cada función tenga el mismo nivel de abstracción. Que evita goto
es una ventaja.
container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();
encuentro que este tipo de programación es mucho más fácil de leer. Cada función en este caso podría escribirse como una función pura, lo cual es mucho más fácil de razonar. La función principal ahora explica qué hace el código solo por el nombre de las funciones, y luego, si lo desea, puede ver sus definiciones para averiguar cómo lo hace.
La regla con goto que usamos es que goto está bien para saltar hacia adelante a un único punto de limpieza de salida en una función. En funciones realmente complejas, relajamos esa regla para permitir otros saltos hacia adelante. En ambos casos, estamos evitando las declaraciones anidadas profundas que a menudo ocurren con la comprobación del código de error, lo que ayuda a la legibilidad y el mantenimiento.
La discusión más cuidadosa y exhaustiva de las declaraciones de goto, sus usos legítimos y construcciones alternativas que se pueden usar en lugar de "declaraciones de goto virtuosas" pero que se pueden abusar tan fácilmente como las declaraciones de goto, es el artículo de Donald Knuth " Programación estructurada con declaraciones de goto ". , en diciembre de 1974, Computing Surveys (volumen 6, no. 4. pp. 261 - 301).
No es sorprendente que algunos aspectos de este documento de 39 años estén anticuados: los aumentos de órdenes de magnitud en el poder de procesamiento hacen que algunas de las mejoras de rendimiento de Knuth sean imperceptibles para problemas de tamaño moderado, y desde entonces se han inventado nuevas construcciones de lenguaje de programación. (Por ejemplo, los bloques try-catch subsumen la Construcción de Zahn, aunque rara vez se usan de esa manera). Pero Knuth cubre todos los lados del argumento, y se debe leer antes de que alguien vuelva a analizar el tema una vez más.
En un módulo Perl, ocasionalmente desea crear subrutinas o cierres sobre la marcha. La cuestión es que, una vez que haya creado la subrutina, ¿cómo llegar a ella? Simplemente podría llamarlo, pero si la subrutina lo usa caller()
, no será tan útil como podría ser. Ahí es donde la goto &subroutine
variación puede ser útil.
sub AUTOLOAD{
my($self) = @_;
my $name = $AUTOLOAD;
$name =~ s/.*:://;
*{$name} = my($sub) = sub{
# the body of the closure
}
goto $sub;
# nothing after the goto will ever be executed.
}
También puede usar esta forma de goto
para proporcionar una forma rudimentaria de optimización de llamadas de cola.
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
$tally *= $n--;
@_ = ($n,$tally);
goto &factorial;
}
(En Perl 5 versión 16 , sería mejor escribirlo como goto __SUB__;
)
Hay un módulo que importará un tail
modificador y uno que importará recur
si no le gusta usar esta forma de goto
.
use Sub::Call::Tail;
sub AUTOLOAD {
...
tail &$sub( @_ );
}
use Sub::Call::Recur;
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
recur( $n-1, $tally * $n );
}
goto
se hacen mejor con otras palabras clave.Como redo
un poco de código:
LABEL: ;
...
goto LABEL if $x;
{
...
redo if $x;
}
O yendo al last
código de un poco desde múltiples lugares:
goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
last if $x;
...
last if $y
...
}
Si es así, ¿por qué?
C no tiene interrupción multinivel / etiquetada, y no todos los flujos de control se pueden modelar fácilmente con la iteración de C y las primitivas de decisión. los gotos recorren un largo camino para corregir estos defectos.
A veces es más claro usar una variable de bandera de algún tipo para efectuar una especie de ruptura de pseudo-multinivel, pero no siempre es superior al goto (al menos un goto permite determinar fácilmente a dónde va el control, a diferencia de una variable de bandera ), y a veces simplemente no desea pagar el precio de rendimiento de las banderas / otras contorsiones para evitar el goto.
libavcodec es una pieza de código sensible al rendimiento. La expresión directa del flujo de control es probablemente una prioridad, ya que tenderá a funcionar mejor.
Encuentro el uso do {} while (false) completamente repugnante. Es concebible que pueda convencerme de que es necesario en algún caso extraño, pero nunca de que sea un código limpio y sensato.
Si debe hacer algún ciclo de este tipo, ¿por qué no hacer explícita la dependencia de la variable de marca?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)
/*empty*/
sea stepfailed = 1
? En cualquier caso, ¿cómo es esto mejor que un do{}while(0)
? En ambos, necesitas break
salir de él (o en el tuyo stepfailed = 1; continue;
). Me parece innecesario
1) El uso más común de goto que conozco es emular el manejo de excepciones en lenguajes que no lo ofrecen, es decir, en C. (El código proporcionado por Nuclear arriba es solo eso). Mire el código fuente de Linux y usted ' veré un bazillion de gotos usados de esa manera; hubo alrededor de 100,000 gotos en el código de Linux según una encuesta rápida realizada en 2013: http://blog.regehr.org/archives/894 . El uso de Goto incluso se menciona en la guía de estilo de codificación de Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Al igual que la programación orientada a objetos se emula utilizando estructuras pobladas con punteros de función, goto tiene su lugar en la programación en C. Entonces, ¿quién tiene razón: Dijkstra o Linus (y todos los codificadores de kernel de Linux)? Es teoría versus práctica básicamente.
Sin embargo, existe el problema habitual de no tener soporte a nivel de compilador y verificaciones de construcciones / patrones comunes: es más fácil usarlos incorrectamente e introducir errores sin verificaciones en tiempo de compilación. Windows y Visual C ++ pero en modo C ofrecen manejo de excepciones a través de SEH / VEH por esta misma razón: las excepciones son útiles incluso fuera de los lenguajes OOP, es decir, en un lenguaje de procedimiento. Pero el compilador no siempre puede guardar su tocino, incluso si ofrece soporte sintáctico para excepciones en el idioma. Considere como ejemplo de este último caso el famoso error "goto fail" de Apple SSL, que acaba de duplicar un goto con consecuencias desastrosas ( 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
Puede tener exactamente el mismo error utilizando excepciones compatibles con el compilador, por ejemplo, en C ++:
struct Fail {};
try {
if (something())
throw Fail();
throw Fail(); // copypasta bug
printf("Never reached\n");
}
catch (Fail&) {
// control jumps here
}
Pero ambas variantes del error pueden evitarse si el compilador analiza y le advierte sobre el código inalcanzable. Por ejemplo, compilar con Visual C ++ en el nivel de advertencia / W4 encuentra el error en ambos casos. Java, por ejemplo, prohíbe el código inalcanzable (¡donde puede encontrarlo!) Por una muy buena razón: es probable que sea un error en el código promedio de Joe. Siempre que la construcción goto no permita objetivos que el compilador no puede entender fácilmente, como los gotos a las direcciones calculadas (**), no es más difícil para el compilador encontrar código inalcanzable dentro de una función con gotos que usar Dijkstra -código aprobado.
(**) Nota al pie: en algunas versiones de Basic es posible pasar de Gotos a números de línea calculados, por ejemplo, GOTO 10 * x donde x es una variable. De manera bastante confusa, en Fortran "goto computarizado" se refiere a una construcción que es equivalente a una declaración de cambio en C. El estándar C no permite gotos computados en el lenguaje, sino solo gotos para etiquetas declaradas estática / sintácticamente. Sin embargo, GNU C tiene una extensión para obtener la dirección de una etiqueta (el operador unario, prefijo &&) y también permite ir a una variable de tipo void *. Consulte https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html para obtener más información sobre este oscuro subtema. El resto de esta publicación no está relacionado con esa oscura característica de GNU C.
Los gotos estándar C (es decir, no calculados) no suelen ser la razón por la que no se puede encontrar el código inalcanzable en tiempo de compilación. La razón habitual es un código lógico como el siguiente. Dado
int computation1() {
return 1;
}
int computation2() {
return computation1();
}
Es tan difícil para un compilador encontrar código inalcanzable en cualquiera de las siguientes 3 construcciones:
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&) {
}
}
(Disculpe mi estilo de codificación relacionado con llaves, pero intenté mantener los ejemplos lo más compactos posible).
Visual C ++ / W4 (incluso con / Ox) no puede encontrar código inalcanzable en ninguno de estos, y como probablemente sepa, el problema de encontrar código inalcanzable es indecidible en general. (Si no me cree sobre eso: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )
Como un problema relacionado, el C goto se puede usar para emular excepciones solo dentro del cuerpo de una función. La biblioteca estándar de C ofrece un par de funciones setjmp () y longjmp () para emular salidas / excepciones no locales, pero tienen algunas desventajas serias en comparación con lo que ofrecen otros idiomas. El artículo de Wikipedia http://en.wikipedia.org/wiki/Setjmp.h explica bastante bien este último problema. Este par de funciones también funciona en Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ), pero casi nadie las usa allí porque SEH / VEH es superior. Incluso en Unix, creo que setjmp y longjmp rara vez se usan.
2) Creo que el segundo uso más común de goto en C es implementar la interrupción multinivel o la continuación multinivel, que también es un caso de uso bastante controvertido. Recuerde que Java no permite ir a la etiqueta, pero permite romper la etiqueta o continuar con la etiqueta. De acuerdo con http://www.oracle.com/technetwork/java/simple-142616.html , este es en realidad el caso de uso más común de gotos en C (90% dicen), pero en mi experiencia subjetiva, el código del sistema tiende usar gotos para el manejo de errores con mayor frecuencia. Quizás en el código científico o donde el sistema operativo ofrece manejo de excepciones (Windows), las salidas de varios niveles son el caso de uso dominante. Realmente no dan detalles sobre el contexto de su encuesta.
Editado para agregar: resulta que estos dos patrones de uso se encuentran en el libro C de Kernighan y Ritchie, alrededor de la página 60 (dependiendo de la edición). Otra cosa a tener en cuenta es que ambos casos de uso implican solo avances. Y resulta que la edición MISRA C 2012 (a diferencia de la edición 2004) ahora permite gotos, siempre y cuando sean solo adelantados.
Algunos dicen que no hay razón para ir a C ++. Algunos dicen que en el 99% de los casos hay mejores alternativas. Esto no es razonamiento, solo impresiones irracionales. Aquí hay un ejemplo sólido donde goto conduce a un buen código, algo así como un bucle mejorado do-while:
int i;
PROMPT_INSERT_NUMBER:
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
goto PROMPT_INSERT_NUMBER;
}
std::cout << "your number is " << i;
Compárelo con el código sin goto:
int i;
bool loop;
do {
loop = false;
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
loop = true;
}
} while(loop);
std::cout << "your number is " << i;
Veo estas diferencias:
{}
Se necesita un bloque anidado (aunque do {...} while
parece más familiar)loop
Se necesita una variable adicional , utilizada en cuatro lugaresloop
loop
no contiene ningún dato, solo controla el flujo de la ejecución, que es menos comprensible que una simple etiquetaHay otro ejemplo
void sort(int* array, int length) {
SORT:
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
goto SORT; // it is very easy to understand this code, right?
}
}
Ahora vamos a deshacernos del "mal" goto:
void sort(int* array, int length) {
bool seemslegit;
do {
seemslegit = true;
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
seemslegit = false;
}
} while(!seemslegit);
}
Verá que es el mismo tipo de uso de goto, es un patrón bien estructurado y no se avanza tanto como la única forma recomendada. Seguramente desea evitar el código "inteligente" como este:
void sort(int* array, int length) {
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
i = -1; // it works, but WTF on the first glance
}
}
El punto es que goto puede ser mal utilizado fácilmente, pero el goto en sí mismo no tiene la culpa. Tenga en cuenta que la etiqueta tiene un alcance de función en C ++, por lo que no contamina el alcance global como en el ensamblaje puro, en el que los bucles superpuestos tienen su lugar y son muy comunes, como en el siguiente código para 8051, donde la pantalla de 7 segmentos está conectada a P1. El programa recorre el segmento de rayos alrededor:
; P1 states loops
; 11111110 <-
; 11111101 |
; 11111011 |
; 11110111 |
; 11101111 |
; 11011111 |
; |_________|
init_roll_state:
MOV P1,#11111110b
ACALL delay
next_roll_state:
MOV A,P1
RL A
MOV P1,A
ACALL delay
JNB P1.5, init_roll_state
SJMP next_roll_state
Hay otra ventaja: goto puede servir como bucles con nombre, condiciones y otros flujos:
if(valid) {
do { // while(loop)
// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket
} while(loop);
} // if(valid)
O puede usar goto equivalente con sangría, por lo que no necesita comentarios si elige sabiamente el nombre de la etiqueta:
if(!valid) goto NOTVALID;
LOOPBACK:
// more than one page of code here
if(loop) goto LOOPBACK;
NOTVALID:;
En Perl, use una etiqueta para "ir a" de un bucle, usando una "última" declaración, que es similar a un salto.
Esto permite un mejor control sobre los bucles anidados.
La etiqueta goto tradicional también es compatible, pero no estoy seguro de que haya demasiados casos en los que esta sea la única forma de lograr lo que desea: las subrutinas y los bucles deberían ser suficientes en la mayoría de los casos.
goto &subroutine
. Que inicia la subrutina con la @_ actual, mientras reemplaza la subrutina actual en la pila.
El problema con 'goto' y el argumento más importante del movimiento de 'programación sin goto' es que, si lo usa con demasiada frecuencia, su código, aunque podría comportarse correctamente, se vuelve ilegible, imposible de mantener, no revisable, etc. En 99.99% de los casos 'goto' conducen a un código de espagueti. Personalmente, no puedo pensar en ninguna buena razón de por qué usaría 'goto'.
goto
). El uso de @ cschol es similar: aunque quizás no esté diseñando un lenguaje en este momento, básicamente está evaluando el esfuerzo del diseñador.
goto
excepto en contextos en los que daría vida a las variables, es más barato que tratar de soportar todo tipo de estructura de control que alguien pueda necesitar. Escribir código con goto
puede no ser tan bueno como usar alguna otra estructura, pero ser capaz de escribir dicho código goto
ayudará a evitar "agujeros en la expresividad", construcciones para las cuales un lenguaje es incapaz de escribir código eficiente.
goto
en el sitio de revisión de código, la eliminación goto
simplifica enormemente la lógica del código.
El GOTO se puede usar, por supuesto, pero hay una cosa más importante que el estilo del código, o si el código es o no legible, debe tenerlo en cuenta cuando lo usa: el código interno puede no ser tan robusto como usted pensar .
Por ejemplo, mire los siguientes dos fragmentos de código:
If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)
Un código equivalente con GOTO
If A == 0 Then GOTO FINAL EndIf
A = 0
FINAL:
Write("Value of A:" + A)
Lo primero que pensamos es que el resultado de ambos bits de código será ese "Valor de A: 0" (por supuesto, suponemos una ejecución sin paralelismo)
Eso no es correcto: en la primera muestra, A siempre será 0, pero en la segunda muestra (con la declaración GOTO) A podría no ser 0. ¿Por qué?
La razón es porque desde otro punto del programa puedo insertar un GOTO FINAL
sin controlar el valor de A.
Este ejemplo es muy obvio, pero a medida que los programas se vuelven más complicados, aumenta la dificultad de ver ese tipo de cosas.
Se puede encontrar material relacionado en el famoso artículo del Sr. Dijkstra "Un caso contra la declaración IR A"
Utilizo goto en el siguiente caso: cuando sea necesario regresar de funciones en diferentes lugares, y antes de regresar, se debe hacer un poco de inicialización:
versión sin goto:
int doSomething (struct my_complicated_stuff *ctx)
{
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
db_disconnect(conn);
return -1;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
free(temp_data);
db_disconnect(conn);
return -2;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -3;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -4;
}
if (ctx->something_else->additional_check) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -5;
}
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return 0;
}
ir a la versión:
int doSomething_goto (struct my_complicated_stuff *ctx)
{
int ret=0;
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
ret=-1;
goto exit_db;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
ret=-2;
goto exit_freetmp;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
ret=-3;
goto exit;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
ret=-4;
goto exit_freekey;
}
if (ctx->something_else->additional_check) {
ret=-5;
goto exit_freekey;
}
exit_freekey:
rsa_free(key);
exit:
pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
free(temp_data);
exit_db:
db_disconnect(conn);
return ret;
}
La segunda versión lo hace más fácil, cuando necesita cambiar algo en las declaraciones de desasignación (cada una se usa una vez en el código), y reduce la posibilidad de omitir cualquiera de ellas, al agregar una nueva rama. Moverlos en una función no ayudará aquí, porque la desasignación se puede hacer en diferentes "niveles".
finally
bloques en C #
finally
). Como alternativa, use goto
s, pero a un punto de salida común , que siempre hace toda la limpieza. Pero cada método de limpieza puede manejar un valor que es nulo o ya está limpio, o está protegido por una prueba condicional, por lo que se omite cuando no es apropiado.
goto
s que todos van al mismo punto de salida, que tiene la misma lógica (que requiere un extra si por recurso, como usted dice). Pero no importa, cuando se usa C
tiene razón: cualquiera que sea la razón por la que el código está en C, es casi seguro que es una compensación que favorece el código más "directo". (Mi sugerencia maneja situaciones complejas en las que cualquier recurso dado puede o no haber sido asignado. Pero sí, exagerar en este caso.)
Edsger Dijkstra, un científico informático que tuvo importantes contribuciones en el campo, también fue famoso por criticar el uso de GoTo. Hay un breve artículo sobre su argumento en Wikipedia .
Es útil para el procesamiento de cadenas de caracteres de vez en cuando.
Imagine algo como este ejemplo de printf-esque:
for cur_char, next_char in sliding_window(input_string) {
if cur_char == '%' {
if next_char == '%' {
cur_char_index += 1
goto handle_literal
}
# Some additional logic
if chars_should_be_handled_literally() {
goto handle_literal
}
# Handle the format
}
# some other control characters
else {
handle_literal:
# Complicated logic here
# Maybe it's writing to an array for some OpenGL calls later or something,
# all while modifying a bunch of local variables declared outside the loop
}
}
Podría refactorizar eso goto handle_literal
a una llamada de función, pero si está modificando varias variables locales diferentes, tendría que pasar referencias a cada una a menos que su idioma admita cierres mutables. Todavía tendría que usar una continue
declaración (que podría decirse que es una forma de goto) después de la llamada para obtener la misma semántica si su lógica hace que un caso no funcione.
También he usado gotos juiciosamente en lexers, típicamente para casos similares. No los necesita la mayor parte del tiempo, pero es bueno tenerlos para esos casos extraños.