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


201

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

Respuestas:


242

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.


15
La forma correcta de salir de los bucles anidados es refactorizar el bucle interno en un método separado.
Jason

129129
@ Jason - Bah. Eso es una carga de toro. Reemplazar gotocon returnes simplemente una tontería. No es "refactorizar" nada, es solo "renombrar" para que las personas que crecieron en un gotoentorno 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.
Chris Lutz

18
Hay situaciones en las que los gotos son importantes: por ejemplo, un entorno C ++ sin excepciones. En la fuente de Silverlight tenemos decenas de miles (o más) de declaraciones Goto para una función segura mediante el uso de macros: los códecs y bibliotecas de medios clave a menudo funcionan a través de valores de retorno y nunca excepciones, y es difícil combinar estos mecanismos de manejo de errores en Una sola forma performante.
Jeff Wilcox

79
Vale la pena señalar que todos break, continue, returnson, básicamente goto, por si empaquetado agradable.
el.pescado

16
No veo cómo do{....}while(0)se supone que es una mejor idea que goto, excepto por el hecho de que funciona en Java.
Jeremy List

906

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 gotoque 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 gotomeme 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 gotoC 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 gotoen 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á gotoa 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 gotohay 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 forbucles 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 #definemucho 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.

  1. El documento de Dijkstra sobre gotodeclaraciones fue escrito para un entorno de programación donde gotoera mucho 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 gotoesto es tan racional como decir "Intenté divertirme una vez pero no me gustó, así que ahora estoy en contra".
  3. Hay usos legítimos de las gotodeclaraciones modernas (anémicas) en el código que no pueden ser reemplazadas adecuadamente por otras construcciones.
  4. Hay, por supuesto, usos ilegítimos de las mismas declaraciones.
  5. También hay usos ilegítimos de las declaraciones de control modernas, como la " godo" abominación en la que dose interrumpe el uso breakde un bucle siempre falso en lugar de a goto. Estos a menudo son peores que el uso juicioso de goto.

42
@pocjoc: escritura de optimización recursiva de cola en C, por ejemplo. Esto surge en la implementación de intérpretes / tiempos de ejecución funcionales del lenguaje, pero es representativo de una categoría interesante de problemas. La mayoría de los compiladores escritos en C utilizan goto por este motivo. Es un uso de nicho que no ocurre en la mayoría de las situaciones, por supuesto, pero en las situaciones que se requiere tiene mucho sentido.
zxq9

66
¿Por qué todo el mundo habla del artículo "Ir a considerado dañino" de Dijkstra sin mencionar la respuesta de Knuth, "Programación estructurada con declaraciones ir a"?
Oblomov

22
@ Nietzche-jou "este es un hombre de paja bastante descarado" [...] Él está usando sarcásticamente una falsa dicotomía para mostrar por qué el estigma es ilógico. Un Strawman es una falacia lógica en la que tergiversas intencionalmente la posición del oponente para que parezca que su posición es fácilmente derrotada. Por ejemplo, "los ateos son solo ateos porque odian a Dios". Una dicotomía falsa es cuando se supone que el extremo opuesto es verdadero cuando existe al menos una posibilidad adicional (es decir, blanco y negro). Por ejemplo, "Dios odia a los homosexuales, por lo tanto, ama a los heteros".
Braden Best

27
@JLRishe Estás leyendo demasiado en algo que ni siquiera está allí. Es solo una verdadera falacia lógica si la persona que lo usó honestamente cree que tiene sentido lógico. Pero lo dijo sarcásticamente, indicando claramente que sabe que es ridículo, y así es como lo está mostrando. No está "usándolo para justificar su punto"; lo está usando para mostrar por qué es ridículo decir que los gotos convierten automáticamente el código en espagueti. Es decir, todavía puedes escribir código de mierda sin gotos. Ese es su punto. Y la falsa dicotomía sarcástica es su método para ilustrarlo de manera humorística.
Braden Best

32
-1 por hacer un argumento muy amplio de por qué el comentario de Dijkstra no es aplicable, mientras se olvida de mostrar cuáles son las ventajas de la gotorealidad (que es la pregunta publicada)
Maarten Bodewes

154

Obedecer las mejores prácticas a ciegas no es una buena práctica. La idea de evitar las gotodeclaraciones 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 gotodeclaraciones, su código sigue siendo perfectamente legible.

En pocas palabras: evitar gotopor el simple hecho de evitar gotoes inútil. Lo que realmente quieres evitar es producir código ilegible. Si su gotocódigo cargado es legible, entonces no tiene nada de malo.


36

Dado gotoque el razonamiento sobre el flujo del programa es difícil 1 (también conocido como "código de espagueti"), gotogeneralmente solo se usa para compensar las características faltantes: el uso de gotopuede 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 gotoes salir de los bucles anidados. E incluso allí, casi siempre es mejor elevar el bucle externo a un método propio y usarlo returnen su lugar.

Aparte de eso, gotoes 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 gotoimplementar algunas restricciones (por ejemplo, gotopueden 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.


44
Solo tengo curiosidad aquí, pero ¿qué pasa con el uso de gotos para limpiar el código? Por limpieza, quiero decir, no solo desasignación de memoria, sino también, por ejemplo, registro de errores. Estaba leyendo un montón de publicaciones, y aparentemente, nadie escribe código que imprima registros ... ¡¿mmm ?!
shiva

37
"Básicamente, debido a la naturaleza defectuosa de Goto (y creo que esto no es controvertido)" -1 y dejé de leer allí.
o0 '.

14
La advertencia contra el goto proviene de la era de la programación estructurada, donde los primeros retornos también se consideraron malvados. Mover bucles anidados a una función cambia un "mal" por otro, y crea una función que no tiene una razón real para existir (fuera de "¡me permitió evitar usar un goto!"). Es cierto que goto es la herramienta más poderosa para producir código de espagueti si se usa sin restricciones, pero esta es una aplicación perfecta para ello; Simplifica el código. Knuth abogó por este uso, y Dijkstra dijo: "No caigas en la trampa de creer que soy terriblemente dogmático sobre el goto".
Barro

55
finally? Entonces, ¿usar excepciones para otras cosas además del manejo de errores es bueno pero usar gotoes malo? Creo que las excepciones tienen un nombre bastante acertado.
Christian

3
@Mud: en los casos que encuentro (a diario), crear una función "que no tenga una razón real para existir" es la mejor solución. Motivo: elimina los detalles de la función de nivel superior, de modo que el resultado tiene un flujo de control fácil de leer. Personalmente, no encuentro situaciones en las que un goto produzca el código más legible. Por otro lado, uso múltiples retornos, en funciones simples, donde alguien más podría preferir ir a un solo punto de salida. Me encantaría ver ejemplos contrarios donde goto es más legible que refactorizar en funciones bien nombradas.
ToolmakerSteve

35

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

2
do{}while(false)Creo que puede considerarse idiomático. No está permitido estar en desacuerdo: D
Thomas Eding

37
@trinithis: Si es "idiomático", es solo por el culto anti-goto. Si lo miras detenidamente, te darás cuenta de que es solo una forma de decirlo 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.
cHao

55
@ThomasEding Eding Hay una excepción a su punto. Si alguna vez hiciste algo de programación C / C ++ y tuviste que usar #define, sabrías que do {} while (0) es uno de los estándares para encapsular múltiples líneas de código. Por ejemplo: #define do {memcpy (a, b, 1); something ++;} while (0) es más seguro y mejor que #define memcpy (a, b, 1); something ++
Ignas2526

10
@ Ignas2526 Usted acaba de mostrar muy bien cómo #definelas cosas son muchas, muchas veces mucho peores que goto
usarlas de

3
+1 para una buena lista de formas en que las personas intentan evitar los gotos, hasta el extremo; He visto menciones de tales técnicas en otros lugares, pero esta es una lista excelente y sucinta. Reflexionando sobre por qué, en los últimos 15 años, mi equipo nunca ha necesitado ninguna de esas técnicas, ni ha usado gotos. Incluso en una aplicación cliente / servidor con cientos de miles de líneas de código, por múltiples programadores. C #, java, PHP, python, javascript. Código de cliente, servidor y aplicación. No es jactancia, ni proselitismo para una posición, justo realmente curiosa por qué algunas personas se encuentran con situaciones que BEG para GOTOS como la solución más clara, y otros no lo hacen ...
ToolmakerSteve

28

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.


3
La interrupción de conmutación es compatible con .NET 2.0 - msdn.microsoft.com/en-us/library/06tc147t(VS.80).aspx
rjzii

9
Solo si el caso no tiene un cuerpo de código. Si tiene código, debe usar la palabra clave goto.
Matthew Whited

26
Esta respuesta es muy divertida: C # eliminó el error porque muchos lo ven como dañino, y este ejemplo usa goto (también visto como dañino por muchos) para revivir el comportamiento original, supuestamente dañino, PERO el resultado general es en realidad menos dañino ( porque el código deja en claro que la falla es intencional).
thomasrutter

9
El hecho de que una palabra clave se escriba con las letras GOTO no lo convierte en un goto. Este caso presentado no es un goto. Es una construcción de declaración de cambio para fallthrough. Por otra parte, no conozco muy bien C #, así que podría estar equivocado.
Thomas Eding

1
Bueno, en este caso es un poco más que un fracaso (porque puedes decir goto case 5:cuándo estás en el caso 1). Parece que la respuesta de Konrad Rudolph es correcta aquí: gotoestá 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 continuesolicitarlo explícitamente.
David Stone

14

#ifdef TONGUE_IN_CHEEK

Perl tiene una gotoque 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 gotopara 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/ccy 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 .)


Creo que esta es la única razón para usar goto en Perl.
Brad Gilbert el

12

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.


1
Quizás quiera leer por qué creo que esto es fundamentalmente incorrecto en stackoverflow.com/questions/46586/…
Konrad Rudolph

15
¡Cualquiera que avance el "todo se compila a JMP de todos modos!" El argumento básicamente no comprende el punto detrás de la programación en un lenguaje de nivel superior.
Nietzche-jou

77
Todo lo que realmente necesitas es restar y ramificar. Todo lo demás es por conveniencia o rendimiento.
David Stone

8

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, gotosiempre 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?


7

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 gotos.

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 gotoantes de hackear el ensamblador.


3
Bueno ... me lleva de vuelta a los días de programación de FORTRAN en una compañía de información financiera. En 2007.
Marcin

55
Se puede abusar de cualquier estructura del lenguaje de manera que sea ilegible o de bajo rendimiento.
SOLO MI OPINIÓN correcta

@JUST: El punto no se trata de legibilidad o de bajo rendimiento, sino de suposiciones y garantías sobre el gráfico de flujo de control. Cualquier abuso de goto sería para un mayor rendimiento (o legibilidad).
Anders Eurenius

3
Diría que una de las razones por las que gotoes ú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.
cHao

1
... una traducción directa del algoritmo requerido al código basado en GOTO puede ser mucho más fácil de validar que un desorden de variables de indicador y construcciones de bucle abusadas.
supercat

7

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.


1
En el ejemplo que diste, preferiría refactorizar eso bastante significativamente. En general, trato de evitar los comentarios de una línea que dicen lo que está haciendo el siguiente fragmento de código. En cambio, divido eso en su propia función que se llama similar al comentario. Si tuviera que realizar tal transformación, entonces esta función le daría una visión general de alto nivel de lo que está haciendo la función, y cada una de las nuevas funciones indicaría cómo hacer cada paso. Creo que es mucho más importante que cualquier oposición a gotoque cada función tenga el mismo nivel de abstracción. Que evita gotoes una ventaja.
David Stone

2
Realmente no has explicado cómo agregar más funciones elimina el goto, los múltiples niveles de sangría y las declaraciones espurias si ...
Ricardo

Se vería así, usando la notación de contenedor estándar: 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.
David Stone

3
El hecho de que alguien tenga que dar ejemplos, de cómo ciertos algoritmos deberían usar naturalmente un goto, ¡es una triste reflexión sobre cuán poco pensamiento algorítmico ocurre hoy! Por supuesto, el ejemplo de @ Ricardo es (uno de muchos) ejemplos perfectos de donde goto es elegante y obvio.
Fattie

6

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.


1
Podría ver que algo así es útil en un lenguaje como C. Sin embargo, cuando tienes el poder de los constructores / destructores de C ++, generalmente no es tan útil.
David Stone

1
"En funciones realmente complejas, relajamos esa regla para permitir otros saltos hacia adelante". Sin un ejemplo, eso suena como si las funciones complejas fueran aún más complejas mediante el uso de saltos. ¿No sería el enfoque "mejor" refactorizar y dividir esas funciones complejas?
MikeMB

1
Este fue el último propósito para el que usé goto. No extraño goto en Java, porque tiene try-finally que puede hacer el mismo trabajo.
Patricia Shanahan

5

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.


3

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 &subroutinevariació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 puede usar esta forma de gotopara 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 tailmodificador y uno que importará recursi 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 );
}

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

Como redoun poco de código:

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

O yendo al lastcódigo de un poco desde múltiples lugares:

goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
  last if $x;
  ...
  last if $y
  ...
}

2

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.


2

De la misma manera, nadie implementó la declaración "COME FROM" ...


8
O en C ++, C #, Java, JS, Python, Ruby ... etc, etc., etc. Solo lo llaman "excepciones".
cHao

2

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*/)

En caso de que no es que /*empty*/sea stepfailed = 1? En cualquier caso, ¿cómo es esto mejor que un do{}while(0)? En ambos, necesitas breaksalir de él (o en el tuyo stepfailed = 1; continue;). Me parece innecesario
Thomas Eding

2

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.


Correcto. El "elefante en la sala" es que el kernel de Linux, por el amor de Dios, como un ejemplo de una base de código crítica mundial, está cargado de goto. Por supuesto que es. Obviamente. El "anti-goto-meme" es solo una curiosidad de hace décadas. Por supuesto, hay una serie de cosas en la programación (especialmente "estática", de hecho "global" y, por ejemplo, "elseif") que pueden ser abusadas por personas no profesionales. Entonces, si tu primo es el programa de aprendizaje 2, diles "oh no uses globals" y "nunca uses elseif".
Fattie

El error de fallo de goto no tiene nada que ver con goto. El problema es causado por la declaración if sin llaves después. Casi cualquier copia de la declaración pegada dos veces habría causado un problema. Considero que ese tipo de brazalete desnudo es mucho más dañino que el goto.
muusbolla

2

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 {...} whileparece más familiar)
  • loopSe necesita una variable adicional , utilizada en cuatro lugares
  • lleva más tiempo leer y comprender el trabajo con el loop
  • el loopno contiene ningún dato, solo controla el flujo de la ejecución, que 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 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:;

1

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.


Creo que la única forma de goto que usarías en Perl es goto &subroutine. Que inicia la subrutina con la @_ actual, mientras reemplaza la subrutina actual en la pila.
Brad Gilbert el

1

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'.


11
Decir "No se me ocurre ninguna razón" en su argumento es, formalmente, en.wikipedia.org/wiki/Argument_from_ignorance , aunque prefiero la terminología "prueba por falta de imaginación".
SOLO MI OPINIÓN correcta

2
@ SOLO MI OPINIÓN correcta: es una falacia lógica solo si se utiliza post-hoc. Desde el punto de vista de un diseñador de idiomas, puede ser un argumento válido para sopesar el costo de incluir una función ( 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.
Konrad Rudolph

1
@KonradRudolph: En mi humilde opinión, tener un lenguaje permitido, gotoexcepto 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 gotopuede no ser tan bueno como usar alguna otra estructura, pero ser capaz de escribir dicho código gotoayudará a evitar "agujeros en la expresividad", construcciones para las cuales un lenguaje es incapaz de escribir código eficiente.
supercat

1
@supercat Me temo que somos de escuelas de diseño de idiomas fundamentalmente diferentes. Me opongo a que los idiomas sean máximamente expresivos al precio de la comprensibilidad (o corrección). Prefiero tener un lenguaje restrictivo que permisivo.
Konrad Rudolph

1
@thb Sí, por supuesto que sí. A menudo hace que el código sea mucho más difícil de leer y razonar. Prácticamente cada vez que alguien publica código que contiene gotoen el sitio de revisión de código, la eliminación gotosimplifica enormemente la lógica del código.
Konrad Rudolph

1

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 FINALsin 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"


66
Este fue a menudo el caso en el BASIC de la vieja escuela. Sin embargo, en las variantes modernas, no está permitido saltar al medio de otra función ... o en muchos casos, incluso más allá de la declaración de una variable. Básicamente (juego de palabras no intencionado), los lenguajes modernos han eliminado en gran medida los GOTO "desenfrenados" de los que Dijkstra estaba hablando ... y la única forma de usarlo como estaba argumentando, es cometer otros pecados atroces. :)
cHao

1

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".


3
Es por eso que tenemos finallybloques en C #
John Saunders

^ como dijo @JohnSaunders. Este es un ejemplo de "usar goto porque un lenguaje carece de la construcción de control apropiada". SIN EMBARGO, es un olor de código requerir MÚLTIPLES puntos de acceso cerca del retorno. Hay un estilo de programación que es más seguro (más fácil de no fastidiar) Y no requiere gotos, que funciona bien incluso en idiomas sin "finalmente": diseñe esas llamadas de "limpieza" para que sean inofensivas cuando no hay limpieza Se requiere arriba. Factorice todo menos la limpieza en un método, que utiliza el diseño de retorno múltiple. Llame a ese método, luego haga las llamadas de limpieza.
ToolmakerSteve

Tenga en cuenta que el enfoque que describo requiere un nivel adicional de llamada al método (pero solo en un idioma que carece finally). Como alternativa, use gotos, 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.
ToolmakerSteve

@ToolmakerSteve esto no es un olor a código; de hecho, es un patrón extremadamente común en C y se considera una de las formas más válidas de usar goto. ¿Desea que cree 5 métodos, cada uno con su propia prueba if innecesaria, solo para manejar la limpieza de esta función? Ahora ha creado código Y aumento de rendimiento. O simplemente podrías usar goto.
muusbolla

@muusbolla - 1) "crear 5 métodos" - No. Estoy sugiriendo un método único que limpia cualquier recurso que tenga un valor no nulo. O vea mi alternativa, que usa gotos 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 Ctiene 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.)
ToolmakerSteve

0

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 .


0

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_literala 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 continuedeclaració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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.