Ejemplos de buenos gotos en C o C ++ [cerrado]

79

En este hilo, miramos ejemplos de buenos usos de gotoen C o C ++. Esta inspirado por una respuesta que la gente votó porque pensaron que estaba bromeando.

Resumen (la etiqueta cambió del original para que la intención sea aún más clara):

infinite_loop:

    // code goes here

goto infinite_loop;

Por qué es mejor que las alternativas:

  • Es específico. gotoes la construcción del lenguaje que causa una rama incondicional. Las alternativas dependen del uso de estructuras que soporten ramas condicionales, con una condición degenerada siempre verdadera.
  • La etiqueta documenta la intención sin comentarios adicionales.
  • El lector no tiene que escanear el código intermedio para los primeros breaks (aunque todavía es posible que un pirata informático sin principios simule continuecon uno temprano goto).

Reglas:

  • Finge que los gotophobes no ganaron. Se entiende que lo anterior no se puede usar en código real porque va en contra del lenguaje establecido.
  • Supongamos que todos hemos oído hablar de 'Goto considerado dañino' y sabemos que goto se puede usar para escribir código espagueti.
  • Si no está de acuerdo con un ejemplo, critíquelo únicamente por sus méritos técnicos ("Porque a la gente no le gusta goto" no es una razón técnica).

Veamos si podemos hablar de esto como adultos.

Editar

Esta pregunta parece haber terminado ahora. Generó algunas respuestas de alta calidad. Gracias a todos, especialmente a aquellos que se tomaron en serio mi pequeño ejemplo de bucle. La mayoría de los escépticos estaban preocupados por la falta de alcance de bloque. Como @quinmars señaló en un comentario, siempre puede colocar llaves alrededor del cuerpo del bucle. Noto de paso eso for(;;)y while(true)tampoco le doy las llaves gratis (y omitirlas puede causar errores molestos). De todos modos, no desperdiciaré más tu poder mental en esta bagatela: puedo vivir con lo inofensivo y lo idiomático for(;;)y while(true)(igual de bien si quiero mantener mi trabajo).

Considerando las otras respuestas, veo que muchas personas ven gotocomo algo que siempre hay que reescribir de otra manera. Por supuesto, puede evitarlo gotointroduciendo un bucle, una bandera adicional, una pila de mensajes de correo ifelectrónico anidados o lo que sea, pero ¿por qué no considerar si gotoes quizás la mejor herramienta para el trabajo? Dicho de otra manera, ¿cuánta fealdad están dispuestas a soportar las personas para evitar usar una función de lenguaje incorporada para el propósito previsto? Mi opinión es que incluso agregar una bandera es un precio demasiado alto a pagar. Me gusta que mis variables representen cosas en los dominios de problemas o soluciones. 'Solo para evitar un goto' no es suficiente.

Aceptaré la primera respuesta que dio el patrón C para ramificar a un bloque de limpieza. En mi opinión, este es el caso más sólido para una gotode todas las respuestas publicadas, ciertamente si lo mides por las contorsiones que tiene que atravesar un enemigo para evitarlo.

fizzer
fuente
11
No entiendo por qué los gotophobes no solo exigen "#define goto report_to_your_supervisor_for_re_education_through_labour" en la parte superior del archivo de inclusión del proyecto. Si siempre está mal, hazlo imposible. De lo contrario, a veces es correcto ...
Steve Jessop
14
"no se puede utilizar en código real"? Lo uso en código "real" cada vez que es la mejor herramienta para el trabajo. "Modismo establecido" es un bonito eufemismo para "dogmatismo ciego".
Dan Moulding
7
Estoy de acuerdo en que "goto" puede ser útil (hay grandes ejemplos a continuación), pero no estoy de acuerdo con su ejemplo específico. Una línea que dice "ir a bucle_infinito" suena como si significara "ir a la parte del código donde vamos a empezar a hacer un bucle para siempre", como en "initialize (); set_things_up (); goto_loop_infinito;" cuando lo que realmente quieres decir es "comenzar la siguiente iteración del ciclo en el que ya estamos", lo cual es completamente diferente. Si está intentando hacer un bucle y su lenguaje tiene construcciones diseñadas específicamente para hacerlo, utilícelas para mayor claridad. while (true) {foo ()} es bastante inequívoco.
Josh
6
Una importante desventaja de que su ejemplo es que no es evidente qué ohter lugares en el código pueden decidir saltar a esta etiqueta. Un bucle infinito "normal" ( for (;;)) no tiene puntos de entrada sorprendentes.
jalf
3
@ György: sí, pero ese no es un punto de entrada.
jalf

Respuestas:

87

Aquí hay un truco que he oído que la gente usa. Sin embargo, nunca lo he visto en la naturaleza. Y solo se aplica a C porque C ++ tiene RAII para hacer esto de manera más idiomática.

void foo()
{
    if (!doA())
        goto exit;
    if (!doB())
        goto cleanupA;
    if (!doC())
        goto cleanupB;

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}
Greg Rogers
fuente
2
¿Qué pasa con esto? (aparte del hecho de que los comentarios no entienden las líneas nuevas) void foo () {if (doA ()) {if (doB ()) {if (doC ()) {/ * todo tuvo éxito * / return; } undoB (); } undoA (); } regreso; }
Artelius
6
Los bloques adicionales provocan una sangría innecesaria que es difícil de leer. Tampoco funciona si una de las condiciones está dentro de un bucle.
Adam Rosenfield
26
Puede ver mucho de este tipo de código en la mayoría de las cosas de Unix de bajo nivel (como el kernel de Linux, por ejemplo). En C, ese es el mejor idioma para la recuperación de errores en mi humilde opinión.
David Cournapeau
8
Como mencionó cournape, el kernel de Linux usa este estilo todo el tiempo .
CesarB
8
No solo el kernel de Linux, eche un vistazo a las muestras de controladores de Windows de Microsoft y encontrará el mismo patrón. En general, esta es una forma en C de manejar excepciones y muy útil :). Por lo general, prefiero solo 1 etiqueta y en pocos casos 2. 3 se pueden evitar en el 99% de los casos.
Ilya
85

La necesidad clásica de GOTO en C es la siguiente

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

No hay una forma sencilla de salir de los bucles anidados sin un goto.

Paul Nathan
fuente
49
La mayoría de las veces, cuando surge esta necesidad, puedo usar una devolución en su lugar. Pero necesita escribir pequeñas funciones para que funcione de esa manera.
Darius Bacon
7
Definitivamente estoy de acuerdo con Darius: ¡refactorice a una función y vuelva en su lugar!
metao
9
@metao: Bjarne Stroustrup no está de acuerdo. En su libro Lenguaje de programación C ++, este es exactamente el ejemplo que se da de un "buen uso" de goto.
paercebal
19
@Adam Rosenfield: Todo el problema con goto, destacado por Dijkstra, es que la programación estructurada ofrece construcciones más comprensibles. Hacer el código más difícil de leer para evitar goto demuestra que el autor no entendió ese ensayo ...
Steve Jessop
5
@Steve Jessop: La gente no solo ha malinterpretado el ensayo, sino que la mayoría de los cultistas "no ir a" no entienden el contexto histórico en el que fue escrito.
SOLO MI OPINIÓN correcta
32

Aquí está mi ejemplo no tonto (de Stevens APITUE) para llamadas al sistema Unix que pueden ser interrumpidas por una señal.

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

La alternativa es un bucle degenerado. Esta versión se lee como en inglés "si la llamada al sistema fue interrumpida por una señal, reiníciela".

fizzer
fuente
3
de esta manera se usa, por ejemplo, el programador de Linux tiene un goto como este, pero yo diría que hay muy pocos casos en los que los goto hacia atrás son aceptables y, en general, deben evitarse.
Ilya
8
@Amarghosh, continuees solo gotouna máscara.
jball
2
@jball ... hmm ... por el bien de la discusión, sí; puede hacer un buen código legible con goto y un código espagueti con continue .. En última instancia, depende de la persona que escribe el código. El punto es que es fácil perderse con goto que con continue. Y los novatos normalmente usan el primer martillo que obtienen para cada problema.
Amarghosh
2
@Amarghosh. Su código 'mejorado' ni siquiera es equivalente al original, se repite para siempre en el caso de éxito.
Fizzer
3
@fizzer oopsie ... se necesitarán dos else breaks (uno para cada if) para que sea equivalente ... qué puedo decir ... gotoo no, tu código es tan bueno como tú :(
Amarghosh
14

Si el dispositivo de Duff no necesita un goto, ¡tú tampoco deberías! ;)

void dsend(int count) {
    int n;
    if (!count) return;
    n = (count + 7) / 8;
    switch (count % 8) {
      case 0: do { puts("case 0");
      case 7:      puts("case 7");
      case 6:      puts("case 6");
      case 5:      puts("case 5");
      case 4:      puts("case 4");
      case 3:      puts("case 3");
      case 2:      puts("case 2");
      case 1:      puts("case 1");
                 } while (--n > 0);
    }
}

código anterior de la entrada de Wikipedia .

Trigo Mitch
fuente
2
Vamos chicos, si van a votar en contra, un breve comentario sobre por qué sería genial. :)
Mitch Wheat
10
Este es un ejemplo de una falacia lógica también conocida como "apelación a la autoridad". Compruébalo en Wikipedia.
Marcus Griep
11
el humor a menudo no se aprecia aquí ...
Steven A. Lowe
8
¿El dispositivo de Duff hace cerveza?
Steven A. Lowe
6
este puede hacer 8 a la vez ;-)
Jasper Bekkers
14

Knuth ha escrito un artículo "Programación estructurada con instrucciones GOTO", puede obtenerlo, por ejemplo, desde aquí . Allí encontrará muchos ejemplos.

zvrba
fuente
Este documento parece profundo. Aunque lo leeré. Gracias
fizzer
2
Ese papel está tan desactualizado que ni siquiera es gracioso. Las suposiciones de Knuth allí simplemente ya no se mantienen.
Konrad Rudolph
3
¿Qué supuestos? Sus ejemplos son tan reales hoy para un lenguaje procedimental como C (los da en algún pseudocódigo) como lo eran en ese momento.
zvrba
8
Estoy decepcionado de que no hayas escrito: Puedes GOTO 'aquí' para conseguirlo, ¡el juego de palabras para mí hubiera sido insoportable para no hacerlo!
RhysW
12

Muy común.

do_stuff(thingy) {
    lock(thingy);

    foo;
    if (foo failed) {
        status = -EFOO;
        goto OUT;
    }

    bar;
    if (bar failed) {
        status = -EBAR;
        goto OUT;
    }

    do_stuff_to(thingy);

OUT:
    unlock(thingy);
    return status;
}

El único caso que utilizo gotoes para saltar hacia adelante, generalmente fuera de los bloques y nunca dentro de los bloques. Esto evita el abuso do{}while(0)y otras construcciones que aumentan el anidamiento, mientras se mantiene un código estructurado legible.

efímero
fuente
5
Creo que esta es la forma típica de C de manejo de errores. No puedo verlo reemplazado bien, mejor legible de otra manera Saludos
Friedrich
Why not ... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :)
Петър Петров
1
@ ПетърПетров Ese es un patrón en C ++ que no tiene análogo en C.
ephemient
O en un lenguaje aún más estructurado que C ++, try { lock();..code.. } finally { unlock(); }pero sí, C no tiene equivalente.
Blindy
12

No tengo nada en contra de los gotos en general, pero puedo pensar en varias razones por las que no querrías usarlos para un bucle como el que mencionaste:

  • No limita el alcance, por lo tanto, las variables temporales que use en el interior no se liberarán hasta más tarde.
  • No limita el alcance, por lo tanto, podría provocar errores.
  • No limita el alcance, por lo tanto, no puede reutilizar los mismos nombres de variable más adelante en el código futuro en el mismo alcance.
  • No limita el alcance, por lo que tiene la posibilidad de omitir una declaración de variable.
  • La gente no está acostumbrada y hará que su código sea más difícil de leer.
  • Los bucles anidados de este tipo pueden generar código espagueti, los bucles normales no conducirán al código espagueti.
Brian R. Bondy
fuente
1
es inf_loop: {/ * cuerpo del bucle * /} goto inf_loop; ¿mejor? :)
quinmars
2
Los puntos 1 a 4 son dudosos para este ejemplo incluso sin los tirantes. 1 Es un bucle infinito. 2 Demasiado vago para abordarlo. 3 Intentar ocultar una variable con el mismo nombre en un ámbito adjunto es una mala práctica. 4. Un goto hacia atrás no puede omitir una declaración.
fizzer
1-4 como con todos los bucles infinitos, generalmente tiene algún tipo de condición de interrupción en el medio. Entonces son aplicables. Re goto bakward no puede omitir una declaración ... no todos los goto están al revés ...
Brian R. Bondy
7

Un buen lugar para usar un goto es un procedimiento que puede abortar en varios puntos, cada uno de los cuales requiere varios niveles de limpieza. Gotophobes siempre puede reemplazar los gotos con código estructurado y una serie de pruebas, pero creo que esto es más sencillo porque elimina la sangría excesiva:

si (! openDataFile ())
  ir a salir;

si (! getDataFromFile ())
  goto closeFileAndQuit;

si (! allocateSomeResources)
  goto freeResourcesAndQuit;

// Trabaja más aquí ...

freeResourcesAndQuit:
   // recursos gratuitos
closeFileAndQuit:
   // cerrar el archivo
dejar:
   // ¡dejar!
Adam Liss
fuente
Me gustaría reemplazar esto con un conjunto de funciones: freeResourcesCloseFileQuit, closeFileQuit, y quit.
RCIX
4
Si creó estas 3 funciones adicionales, deberá pasar punteros / identificadores a los recursos y archivos que se liberarán / cerrarán. Probablemente haría que freeResourcesCloseFileQuit () llame a closeFileQuit (), que a su vez llamaría a quit (). Ahora tiene que mantener 4 funciones estrechamente acopladas, y probablemente 3 de ellas se llamarán como máximo una vez: desde la función única anterior. Si insiste en evitar goto, IMO, los bloques if () anidados tienen menos gastos generales y son más fáciles de leer y mantener. ¿Qué ganas con 3 funciones extra?
Adam Liss
7

@ fizzer.myopenid.com: su fragmento de código publicado es equivalente a lo siguiente:

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

Definitivamente prefiero esta forma.

Trigo Mitch
fuente
1
Bien, sé que la declaración de ruptura es una forma más aceptable de goto ..
Mitch Wheat
10
Para mis ojos, es confuso. Es un bucle en el que no espera ingresar en la ruta de ejecución normal, por lo que la lógica parece al revés. Aunque es cuestión de opinión.
fizzer
1
¿Hacia atrás? Empieza, continúa, fracasa. Creo que esta forma expresa más obviamente sus intenciones.
Mitch Wheat
8
Estoy de acuerdo con fizzer aquí en que el goto proporciona una expectativa más clara en cuanto al valor de la condición.
Marcus Griep
4
De qué manera este fragmento es más fácil de leer que el de fizzer.
erikkallen
6

Aunque he llegado a odiar este patrón con el tiempo, está arraigado en la programación COM.

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error}
...
HRESULT SomeMethod(IFoo* pFoo) {
  HRESULT hr = S_OK;
  IfFailGo( pFoo->PerformAction() );
  IfFailGo( pFoo->SomeOtherAction() );
Error:
  return hr;
}
JaredPar
fuente
5

Aquí hay un ejemplo de un buen goto:

// No Code
FlySwat
fuente
2
er, no gotos? (intento fallido de gracioso: d)
moogs
9
Esto no es realmente útil
Joseph
Pensé que esto era gracioso. Bien hecho. +1
Fantastic Mr Fox
@FlySwat FYI: Encontré esto en la cola de revisión de baja calidad. Considere editar.
Paul
1

He visto que goto se usa correctamente, pero las situaciones suelen ser feas. Es solo cuando el uso de gotosí mismo es mucho menos peor que el original. @Johnathon Holland, el problema es que tu versión es menos clara. la gente parece tener miedo de las variables locales:

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

Y prefiero bucles como este, pero algunas personas prefieren while(true).

for (;;)
{
    //code goes here
}
Charles Beattie
fuente
2
Nunca, nunca, compare un valor booleano con verdadero o falso. Ya es un booleano; simplemente use "doBsuccess = doAsuccess && doB ();" y "if (! doCsuccess) {}" en su lugar. Es más simple y lo protege de los programadores inteligentes que escriben cosas como "#define true 0" porque, bueno, porque pueden. También lo protege de los programadores que mezclan int y bool, y luego asumen que cualquier valor distinto de cero es verdadero.
Adam Liss
Gracias y se == truequitó el punto . De todos modos, esto está más cerca de mi estilo real. :)
Charles Beattie
0

Mi queja sobre esto es que pierde el alcance del bloque; cualquier variable local declarada entre los gotos permanece en vigor si alguna vez se rompe el ciclo. (Tal vez esté asumiendo que el ciclo se ejecuta para siempre; sin embargo, no creo que eso sea lo que estaba haciendo el escritor de la pregunta original).

El problema del ámbito es más un problema con C ++, ya que algunos objetos pueden depender de que se llame a su dtor en los momentos adecuados.

Para mí, la mejor razón para usar goto es durante un proceso de inicialización de varios pasos donde es vital que todos los inits se retiren si uno falla, a la:

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;
Jim Nelson
fuente
0

Yo mismo no uso los goto, sin embargo, una vez trabajé con una persona que los usaría en casos específicos. Si mal no recuerdo, su razón de ser se basaba en problemas de rendimiento; también tenía reglas específicas sobre cómo hacerlo . Siempre en la misma función, y la etiqueta siempre estaba DEBAJO de la instrucción goto.

user25306
fuente
4
Datos adicionales: tenga en cuenta que no puede usar goto para salir de la función, y que en C ++, está prohibido omitir la construcción de un objeto a través de un goto
paercebal
0
#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}
ConflictoSephiroth
fuente
-1

@ Greg:

Por qué no hacer su ejemplo así:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}
FlySwat
fuente
13
Agrega niveles innecesarios de sangría, que pueden volverse realmente complicados si tiene una gran cantidad de posibles modos de falla.
Adam Rosenfield
6
Tomaría sangrías en lugar de goto cualquier día.
FlySwat
13
Además, tiene muchas más líneas de código, tiene una llamada de función redundante en uno de los casos y es mucho más difícil agregar un doD correctamente.
Patrick
6
Estoy de acuerdo con Patrick: he visto este modismo utilizado con varias condiciones que anidaban a una profundidad de aproximadamente 8 niveles. Muy asqueroso. Y muy propenso a errores, especialmente cuando se intenta agregar algo nuevo a la mezcla.
Michael Burr
11
Este código es simplemente gritar "Tendré un error en las próximas semanas", alguien se olvidará de deshacer algo. Necesitas todos tus deshacer en el mismo lugar. Es como "finalmente" en los idiomas nativos de OO.
Ilya