¿Alguna vez es ventajoso usar 'goto' en un lenguaje que admite bucles y funciones?Si es así, ¿por qué?

StackOverflow https://stackoverflow.com/questions/24451

Pregunta

Durante mucho tiempo he tenido la impresión de que goto nunca debe usarse si es posible.Mientras examinaba libavcodec (que está escrito en C) el otro día, noté múltiples usos del mismo.¿Alguna vez es ventajoso usar goto en un lenguaje que admita bucles y funciones?Si es así, ¿por qué?

¿Fue útil?

Solución

Hay algunas razones para usar la declaración "goto" que yo sepa (algunos ya han hablado de esto):

Salir limpiamente de una función

A menudo, en una función, puede asignar recursos y necesitar 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án a la etiqueta de limpieza.De esta manera, no es necesario escribir código de limpieza en cada "punto de salida" de la función.

Salir de bucles anidados

Si estás en un bucle anidado y necesitas salir de él todo bucles, un goto puede hacer esto mucho más limpio y simple que las declaraciones break y las comprobaciones if.

Mejoras de rendimiento de bajo nivel

Esto solo es válido en código de rendimiento crítico, pero las declaraciones goto se ejecutan muy rápidamente y pueden darle un impulso al moverse por una función.Sin embargo, esto es un arma de doble filo, porque un compilador normalmente 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 única función.

Otros consejos

Todos los que son anti-goto cita, directa o indirectamente, la obra de Edsger Dijkstra GoTo se considera perjudicial artículo para fundamentar su posición.Lástima que el artículo de Dijkstra haya prácticamente nada que ver con el camino goto Las declaraciones se utilizan hoy en día 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-Memeless ahora raya en una religión, hasta sus escrituras dictadas desde lo alto, sus sumos sacerdotes y el rechazo (o algo peor) de los percibidos como herejes.

Pongamos en contexto el artículo de Dijkstra para arrojar un poco de luz sobre el tema.

Cuando Dijkstra escribió su artículo, los lenguajes populares de la época eran procedimentales no estructurados como BASIC, FORTRAN (los dialectos anteriores) y varios lenguajes ensambladores.Era bastante común que las personas que usaban lenguajes de nivel superior saltaran en toda su base de código en hilos de ejecución retorcidos y retorcidos que dieron lugar al término "código espagueti".Puedes ver esto saltando a el clásico juego de trekking escrito por Mike Mayfield y tratando de descubrir cómo funcionan las cosas.Tómate unos momentos para revisar eso.

ESTE es "el uso desenfrenado de la declaración ir a" contra lo que Dijkstra criticaba en su artículo de 1968. ESTE Es el entorno en el que vivió lo que le llevó a escribir ese artículo.La capacidad de saltar a cualquier lugar que desee en su código en cualquier punto que desee era lo que criticaba y exigía que se detuviera.Comparando eso con los poderes anémicos de goto en C u otros lenguajes más modernos es simplemente ridículo.

Ya puedo escuchar los cánticos elevados de los cultistas mientras se enfrentan al hereje."Pero", corearán, "puedes hacer que el código sea muy difícil de leer con goto en C." ¿Ah, sí?Puedes hacer que el código sea muy difícil de leer sin goto también.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 un goto a la vista, por lo que debe ser fácil de leer, ¿no?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 allí tampoco.Por 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 la que lo hace.Son los malos programadores los que causan esto.Y los malos programadores, como puedes ver en el punto anterior, pueden hacer cualquier Característica del idioma ilegible e inutilizable.Como el for bucles allí arriba.(Puedes verlos, ¿verdad?)

Ahora bien, para ser justos, es más fácil abusar de algunas construcciones del lenguaje que de otras.Sin embargo, si eres un programador de C, observaría mucho más de cerca aproximadamente el 50% de los usos de #define mucho antes de emprender una cruzada contra goto!

Entonces, para aquellos que se han molestado en leer hasta aquí, hay varios puntos clave a tener en cuenta.

  1. El artículo de Dijkstra sobre goto declaraciones fue escrito para un entorno de programación donde goto era un lotepotencialmente más dañino que en la mayoría de los lenguajes modernos que no son ensambladores.
  2. Desechar automáticamente todos los usos de goto Debido a que esto es tan racional como decir "Traté de divertirme una vez, pero no me gustó, así que ahora estoy en contra".
  3. Hay usos legítimos del término moderno (anémico) goto declaraciones en código que no pueden ser reemplazadas adecuadamente por otras construcciones.
  4. Por supuesto, existen usos ilegítimos de las mismas declaraciones.
  5. También existen usos ilegítimos de las declaraciones de control modernas como "godo"Abominación donde un siempre-falso do el bucle se rompe por el uso break en lugar de un goto.Estos son a menudo peores que el uso juicioso de goto.

Obedecer ciegamente las mejores prácticas no es una buena práctica.La idea de evitar goto Las declaraciones como forma principal de control de flujo son evitar producir código espagueti ilegible.Si se usan con moderación y en los lugares correctos, a veces pueden ser la forma más sencilla y clara de expresar una idea.Walter Bright, creador del compilador Zortech C++ y del lenguaje de programación D, los utiliza con frecuencia, pero con prudencia.Incluso con el goto declaraciones, su código sigue siendo perfectamente legible.

Línea de fondo:evitando goto por el bien de evitar goto No tiene sentido.Lo que realmente quieres evitar es producir código ilegible.Si tu goto-el código cargado es legible, entonces no tiene nada de malo.

Desde goto dificulta el razonamiento sobre el flujo del programa1 (alias.“código espagueti”), goto generalmente solo se usa para compensar las características faltantes:El uso de goto En realidad, puede ser aceptable, pero sólo si el lenguaje no ofrece una variante más estructurada para obtener el mismo objetivo.Tomemos el ejemplo de la duda:

La regla que usamos con goto es que goto está bien para saltar hacia un único punto de limpieza de salida en una función.

Esto es cierto, pero sólo si el lenguaje no permite el manejo estructurado de excepciones con código de limpieza (como RAII o finally), que hace mejor el mismo trabajo (ya que está diseñado especialmente para hacerlo), o cuando hay una buena razón para no emplear el manejo estructurado de excepciones (pero nunca tendrá este caso excepto en un nivel muy bajo).

En la mayoría de los demás idiomas, el único uso aceptable de goto es salir de bucles anidados.E incluso allí casi siempre es mejor levantar el bucle exterior con un método propio y utilizarlo. return en cambio.

Aparte de eso, goto es una señal de que no se ha pensado lo suficiente en un fragmento de código en particular.


1 Lenguajes modernos que soportan goto implementar algunas restricciones (p. ej. goto puede no entrar o salir de funciones) pero el problema fundamentalmente sigue siendo el mismo.

Por cierto, lo mismo también se aplica a otras características del lenguaje, sobre todo a las excepciones.Y generalmente existen reglas estrictas para usar estas características solo cuando se indique, como la regla de no usar excepciones para controlar el flujo de programas no excepcionales.

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

En C# cambiar declaración no permite caídas.Entonces ir a se utiliza para transferir el control a una etiqueta específica de la caja del interruptor o al por defecto etiqueta.

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 a la regla de "no fallar".Se permite el error si una declaración de caso no tiene código.

#ifdef TONGUE_IN_CHEEK

Perl tiene un goto que le permite implementar llamadas de cola de pobre.:-PAG

sub factorial {
    my ($n, $acc) = (@_, 1);
    return $acc if $n < 1;
    @_ = ($n - 1, $acc * $n);
    goto &factorial;
}

#endif

Bien, entonces eso no tiene nada que ver con las C. 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 similar.Se trata de usar, no de abusar.

(El mismo comentario puede aplicarse a longjmp, excepciones, call/cc, y similares: tienen usos legítimos, pero se puede abusar fácilmente de ellos.Por ejemplo, lanzar una excepción simplemente para escapar de una estructura de control profundamente anidada, en circunstancias completamente no excepcionales).

He escrito más de unas pocas líneas en lenguaje ensamblador a lo largo de los años.En última instancia, todos los lenguajes de alto nivel se compilan en gotos.Vale, llámalos "ramas" o "saltos" o lo que sea, pero son gotos.¿Alguien puede escribir un ensamblador sin goto?

Ahora, claro, puedes indicarle a un programador de Fortran, C o BASIC que hacer alboroto con gotos es una receta de espagueti a la boloñesa.La respuesta, sin embargo, no es evitarlos, sino utilizarlos con cuidado.

Se puede utilizar un cuchillo para preparar comida, liberar a alguien o matar a alguien.¿Prescindimos de los cuchillos por miedo a estos últimos?Del mismo modo el ir a:usado descuidadamente obstaculiza, usado con cuidado ayuda.

Echa un vistazo a Cuándo utilizar Goto al programar en C:

Aunque el uso de goto es casi siempre una mala práctica de programación (seguramente puedes encontrar una mejor manera de hacer XYZ), hay ocasiones en las 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 en realidad sólo se aplica a C.Si estás usando C++, no hay ninguna razón sólida 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 a lo largo de su código, entonces ir a puede ser una buena opción.

¿Que quiero decir?Es posible que tengas algún código parecido 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 das cuenta de que necesitas cambiar tu código de limpieza.Luego tienes que seguir adelante y hacer 4 cambios.Ahora, podrías decidir que puedes 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 luego apunte a NULL a menos que pase un puntero a un puntero.En muchos casos, no volverás a utilizar ese puntero de todos modos, por lo que puede que eso no sea una preocupación importante.Por otro lado, si agrega un nuevo puntero, identificador de archivo u otra cosa que necesita limpieza, necesitará cambiar su función de limpieza nuevamente;y luego necesitarás cambiar los argumentos de esa función.

Mediante el uso 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 el código que sigue al final tiene acceso a todo lo que necesita para realizar la limpieza y ha logrado reducir considerablemente la cantidad de puntos de cambio.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 regreses accidentalmente de la función sin limpiar.

Además, dado que goto solo se usa para saltar a un único punto, no es como si estuvieras creando una masa de código espagueti saltando hacia adelante y hacia atrás en un intento de simular llamadas a funciones.Más bien, goto en realidad 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 "¿tienes que usarlo?" sino "¿es la mejor opción?"

Una de las razones por las que goto es malo, además del estilo de codificación, es que puedes usarlo para crear superposición, pero no anidado bucles:

loop1:
  a
loop2:
  b
  if(cond1) goto loop1
  c
  if(cond2) goto loop2

Esto crearía una estructura de flujo de control extraña, pero posiblemente legal, donde es posible una secuencia como (a, b, c, b, a, b, a, b, ...), lo que hace infelices a los piratas informáticos del compilador.Aparentemente, existen varios trucos de optimización inteligentes que se basan en que este tipo de estructura no se produzca.(Debería revisar mi copia del libro del dragón...) El resultado de esto podría (usando algunos compiladores) que no se realicen otras optimizaciones para el código que contiene gotos.

Podría ser útil si usted saber simplemente, "oh, por cierto", persuade al compilador para que emita código más rápido.Personalmente, preferiría intentar 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.

Me parece gracioso que algunas personas lleguen tan lejos como para 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 los que goto es la mejor opción para expresar un algoritmo?

Para ilustrar, les 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 puede sobrescribir a voluntad (lo que afecta el rendimiento pero no la corrección).

Cada depósito de la tabla hash tiene 4 ranuras y tengo varios criterios para decidir qué elemento sobrescribir cuando un depósito está lleno.Ahora mismo esto significa hacer hasta tres pasadas por 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 bien, si no usara goto, ¿cómo se verí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 cada vez 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 if espurias 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 más fácil de escribir y comprender...Estoy seguro de que hay muchos más, así que no pretendas conocer todos los casos en los que goto es útil, menospreciando los buenos que no se te ocurran.

La regla que usamos con goto es que goto está bien para saltar hacia un único punto de limpieza de salida en una función.En funciones realmente complejas relajamos esa regla para permitir que otras avancen.En ambos casos, evitamos declaraciones if profundamente anidadas que a menudo ocurren con la verificación de códigos de error, lo que ayuda a la legibilidad y el mantenimiento.

La discusión más reflexiva y exhaustiva sobre las declaraciones goto, sus usos legítimos y construcciones alternativas que pueden usarse en lugar de "declaraciones goto virtuosas" pero de las que se puede abusar tan fácilmente como las declaraciones goto, es el artículo de Donald Knuth "Programación estructurada con declaraciones goto", en Computing Surveys de diciembre de 1974 (volumen 6, núm.4.páginas.261 - 301).

No sorprende que algunos aspectos de este documento de hace 39 años estén fechados:Los aumentos de órdenes de magnitud en la potencia 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 lenguajes de programación.(Por ejemplo, los bloques try-catch subsumen el constructo de Zahn, aunque rara vez se usan de esa manera). Pero Knuth cubre todos los lados del argumento y debería ser obligatorio leerlo antes de que alguien vuelva a repetir el tema.

En un módulo Perl, ocasionalmente desea crear subrutinas o cierres sobre la marcha.La cuestión es que una vez creada la subrutina, ¿cómo se llega a ella?Podrías simplemente llamarlo, pero luego, si la subrutina usa caller() No será tan útil como podría ser.Ahí es donde el goto &subroutine la variación puede ser útil.

Aquí hay un ejemplo rápido:

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 puedes utilizar 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 eso estaría mejor escrito como goto __SUB__; )

Hay un módulo que importará un tail modificador y uno que importará recur Si no te 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 );
}

La mayoría de las otras razones para usar goto se hacen mejor con otras palabras clave.

Como redoescribiendo un poco de código:

LABEL: ;
...
goto LABEL if $x;
{
  ...
  redo if $x;
}

O ir al last de un poco de código de 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 rupturas multinivel/etiquetadas, y no todos los flujos de control pueden modelarse fácilmente con las primitivas de iteración y decisión de C.gotos contribuyen en gran medida a corregir estos defectos.

A veces es más claro usar una variable de bandera de algún tipo para efectuar una especie de ruptura pseudo-multinivel, pero no siempre es superior a 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 quieres pagar el precio del rendimiento de las banderas/otras contorsiones para evitar el goto.

libavcodec es un fragmento de código sensible al rendimiento.La expresión directa del flujo de control probablemente sea una prioridad, porque tenderá a funcionar mejor.

Menos mal que nadie implementó nunca la declaración "VENIDO DE"....

El uso do{} while(false) me parece absolutamente repugnante.Es posible que pueda convencerme de que es necesario en algún caso extraño, pero nunca de que sea un código limpio y sensible.

Si debe realizar algún bucle de este tipo, ¿por qué no hacer explícita la dependencia de la variable de bandera?

for (stepfailed=0 ; ! stepfailed ; /*empty*/)

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 legible o no, que debes tener en cuenta cuando lo uses: Es posible que el código interno no sea tan sólido como crees..

Por ejemplo, observe los dos fragmentos de código siguientes:

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" (suponemos una ejecución sin paralelismo, claro)

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 GO TO"

1) El uso más común de goto que conozco es emular el manejo de excepciones en lenguajes que no lo ofrecen, concretamente en C.(El código proporcionado por Nuclear arriba es solo eso). Mire el código fuente de Linux y verá millones de gotos utilizados de esa manera;Según una encuesta rápida realizada en 2013, había alrededor de 100.000 gotos en el código de Linux: 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.Así como la programación orientada a objetos se emula utilizando estructuras llenas de punteros de función, goto tiene su lugar en la programación C.Entonces, ¿quién tiene razón?¿Dijkstra o Linus (y todos los codificadores del kernel de Linux)?Es teoría vs.practicar básicamente.

Sin embargo, existe el problema habitual de no tener soporte a nivel de compilador y comprobaciones 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 procesal.Pero el compilador no siempre puede salvarle el camino, incluso si ofrece soporte sintáctico para excepciones en el lenguaje.Considere como ejemplo del último caso el famoso error "goto fail" de Apple SSL, que simplemente duplicó 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 admitidas por el compilador, p.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 se pueden evitar si el compilador analiza y advierte sobre el código inalcanzable.Por ejemplo, al compilar con Visual C++ en el nivel de advertencia /W4 se encuentra el error en ambos casos.Java, por ejemplo, prohíbe el código inalcanzable (¡donde pueda encontrarlo!) por una muy buena razón:es probable que sea un error en el código de Joe promedio.Siempre que la construcción goto no permita objetivos que el compilador no pueda descifrar fácilmente, como ir a 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:Ir a números de línea calculados es posible en algunas versiones de Basic, p.GOTO 10*x donde x es una variable.De manera bastante confusa, en Fortran "goto calculado" se refiere a una construcción que es equivalente a una declaración de cambio en C.El estándar C no permite ir a etiquetas declaradas estáticamente/sintácticamente en el lenguaje, sino solo ir a etiquetas declaradas estáticamente/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*.Ver 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 se ocupa de esa oscura característica de GNU C.

Estándar C (es decir,no calculados) los gotos no suelen ser la razón por la que no se puede encontrar código inalcanzable en el momento de la 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 igual de 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 las llaves, pero traté de mantener los ejemplos lo más compactos posible).

Visual C++ /W4 (incluso con /Ox) no logra 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 crees sobre eso: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf)

Como problema relacionado, C goto se puede utilizar para emular excepciones solo dentro del cuerpo de una función.La biblioteca C estándar ofrece un par de funciones setjmp() y longjmp() para emular salidas/excepciones no locales, pero tienen algunos inconvenientes graves en comparación con lo que ofrecen otros lenguajes.El artículo de Wikipedia http://en.wikipedia.org/wiki/Setjmp.h explica bastante bien esta última cuestión.Este par de funciones también funciona en Windows (http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx), pero casi nadie los utiliza porque SEH/VEH es superior.Incluso en Unix, creo que setjmp y longjmp rara vez se utilizan.

2) Creo que el segundo uso más común de goto en C es implementar una interrupción multinivel o una continuación multinivel, que tampoco es un caso de uso bastante controvertido.Recuerde que Java no permite ir a etiqueta, pero permite romper etiqueta o continuar etiqueta.De acuerdo a 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 a usar gotos para el manejo de errores con más frecuencia.Quizás en el código científico o donde el sistema operativo ofrece manejo de excepciones (Windows), las salidas de varios niveles sean el caso de uso dominante.Realmente no dan ningún detalle 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 (según la edición).Otra cosa a tener en cuenta es que ambos casos de uso implican solo pasos directos.Y resulta que la edición MISRA C 2012 (a diferencia de la edición 2004) ahora permite gotos, siempre que sean solo delanteros.

En Perl, se utiliza una etiqueta para "ir a" desde un bucle, utilizando una instrucción "última", que es similar a romper.

Esto permite un mejor control sobre los bucles anidados.

El tradicional ir etiqueta también es compatible, pero no estoy seguro de que haya muchos 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.

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 se comporte correctamente, se vuelve ilegible, no se puede mantener, no se puede revisar, etc.En el 99,99% de los casos, 'goto' conduce al código espagueti.Personalmente, no se me ocurre ninguna buena razón por la que utilizaría 'goto'.

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 sobre Wikipedia.

Yo uso goto en el siguiente caso:cuando es necesario regresar de funciones en diferentes lugares, y antes de regresar es necesario realizar alguna desinicialización:

versión sin ir:

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 realizar en diferentes "niveles".

Algunos dicen que no hay razón para ir a C++.Algunos dicen que en el 99% de los casos existen mejores alternativas. Esto no es razonamiento, sólo impresiones irracionales. Aquí hay un ejemplo sólido en el que goto conduce a un código agradable, algo así como un bucle do- while mejorado:

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 libre:

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:

  • anidado {} Se necesita un bloque (aunque do {...} while parece más familiar)
  • extra loop Se necesita variable, utilizada en cuatro lugares.
  • lleva más tiempo leer y comprender el trabajo con el loop
  • el loop no contiene ningún dato, solo controla el flujo de ejecución, lo cual es menos comprensible que una simple etiqueta

Hay 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 deshagámonos del goto "malvado":

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á, es el mismo tipo de uso de goto, es un patrón bien estructurado y no es el mismo tipo de promoción que muchos promueven como la única forma recomendada.Seguramente querrás evitar códigos "inteligentes" 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 fácilmente abusado, pero goto en sí 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 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 un segmento de relámpago:

; 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:;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top