2009-07-02 14 views
17

me encontré con el siguiente código en un programa en C:while (1) .. romper en lugar de Goto

while (1) 
{ 
    do_something(); 
    if (was_an_error()) break; 

    do_something_else(); 
    if (was_an_error()) break; 

    [...] 

    break; 
} 
[cleanup code] 

Aquí while(1) se utiliza como emulación local de "fin". También puede escribir esto utilizando goto s:

do_something() 
if (was_an_error()) goto out; 

do_something_else() 
if (was_an_error()) goto out; 

[...] 
out: 
[cleanup code] 

pensé que la solución Goto es un lenguaje habitual. He visto varias apariciones de este modismo en las fuentes del kernel y también se menciona en el libro "Code Reading" de Diomidis Spinellis.

Mi pregunta es: ¿Qué solución es mejor? ¿Hay alguna razón específica para usar la solución while(1)?

Pregunta 943826 no contesta mi pregunta.

+20

Usar while (1) para esto hace que quien lea el código espere que se ejecute "infinitamente" ... ¿Por qué no utilizar do {} while (0)? –

+1

@Tal Pressman: ¿por qué sería eso diferente? –

+2

@Earwicker, con esa opción es obvio que se ejecutará al menos una vez, y que no se ejecutará nuevamente porque la condición es "while (0)". El confuso "while (1)" no aparece. –

Respuesta

33

El revultion aparentemente universal a GOTO es debido en gran parte a la carta de Edsger Dijkstra "Ir a la Declaración Considerado nocivas".

Si usted está decidido a no utilizar Goto, algo así como

do { 
    ... 
while(0); 

es probablemente más seguro que while (1) {...}, ya que garantiza que usted no inadvertidamente bucle (y si son inadvertidamente bucle, con while (1) probablemente estés inadvertidamente haciendo bucles infinitamente).

La ventaja de que (ab) usar do/break/while o while/break para este propósito es over goto es que no se salta por encima de el constructo - goto se puede usar para saltar a una etiqueta anterior dentro de la misma función.

La desventaja de que do/break/while, etc. tiene over over es que está limitado a un punto de salida (inmediatamente después del loop). En algunos casos, es posible que necesite una limpieza por etapas: por ejemplo, cuando abre un manejador de archivo, malloc algo de memoria, lee del archivo ... si la lectura falla, debe limpiar el malloc. Si el malloc falla, no es necesario que lo limpie, pero aún necesita limpiar el identificador del archivo. Con goto, puede tener una etiqueta por etapa de limpieza y saltar exactamente al punto correcto dependiendo de dónde ocurrió el error.

En mi opinión, evitar ciegamente a GOTO debido al odio prevaleciente es más dañino que razonar cuidadosamente sobre un caso para su uso caso por caso. Una regla general que uso es "¿lo hace el kernel de Linux? Si es así, no puede ser que malo". Sustituye al kernel de Linux con cualquier otro buen ejemplo de ingeniería de software moderna.

+0

+1 No he notado su respuesta de alguna manera :-) – liori

+2

En realidad, no es solo kernel de Linux, ejemplos de controladores de Windows de MS están llenos de goto, los controladores de kernel Unix de Solaris también lo usan, el código kernel de BSD, etc. goto es usado y usado ampliamente principalmente por personas que saben lo que están haciendo. – Ilya

+5

He visto código usando el truco 'do {... break ...} while (0);' y puedo dar fe de que dificulta la legibilidad sin comprarle nada. Si está haciendo un 'goto' y de todos modos lo está haciendo aquí, al menos explíquelo. Y no hay nada de malo en 'goto' en el código C para fines de manejo/limpieza de errores. –

7

Aunque generalmente se desaconseja el uso de goto, algunas situaciones raras como la suya son un lugar donde las mejores prácticas no son las mejores.

Por lo tanto, si goto hace el código más claro lo usaría. usar un ciclo while (verdadero) para emular goto es algo antinatural. Lo que realmente necesitas es un goto!

+2

+1 El uso de GOTO es común en los sistemas integrados para limpiar después de una falla. –

+2

+1 El uso de goto no se desalienta "solo porque". Se desaconseja evitar el código de spaghetti y mejorar la claridad. La regla real es que la función debe ser clara y simple, por lo que usar una alternativa aún más difícil de capturar para goto no es una buena opción para IMO. Cualquier cosa que involucre bucles de una sola vez entraría en esta categoría. –

3

Normalmente, los GOTO se consideran malos pero en algunos lugares donde solo hay Saltos hacia delante a través de GOTO, no son TAN malos. Las personas evitan GOTO como la peste, pero un uso bien pensado de GOTO a veces es una mejor solución en mi humilde opinión.

2

Creo que este uso (para la gestión de recursos) de goto está bien.

1

Me gusta el enfoque while (1). Lo uso yo mismo. Especialmente, cuando el ciclo se puede repetir al continuar, p. cuando un elemento se procesa dentro de dicho bucle, y se realiza en múltiples enfoques.

1

Nunca use un bucle de condición con una condición permanentemente verdadera. Dado que la condición siempre es verdadera, ¿por qué usar un bucle condicional?

Las condiciones permanentemente verdaderas se representan más directamente mediante un goto.

+1

Encuentro que (1) + pausa + continúo perfectamente legible para mí ... en oposición a goto, que es IMO ilegible. –

+0

@ emg-2 Puedo leer ambos. Pero "mientras (1)" me comunica una idea falsa de un ciclo infinito (que he visto en otras situaciones) y no una función simple que se ejecuta solo una vez. "goto" es mejor para ese propósito. Todas las funciones son una secuencia, un bucle o un condicional. –

+0

Entonces, ¿está diciendo que el bucle de mensajes de una aplicación estaría mejor representado con un goto al comienzo del manejador de mensajes en lugar de while (1) o para (;;)? –

10

Poner el código en una función separada, y usar return para salir temprano es otra forma de hacerlo, con la ventaja de la fácil integración de un código de retorno que indica la naturaleza de la falla.

+1

El problema no es la salida anticipada, es la limpieza. En un init de varios pasos, eventualmente tendrá que limpiar/desasignar lo que ya se asignó con éxito. – shodanex

+1

Esto es lo que iba a sugerir. De lo contrario, usaría goto over mientras (1), porque mientras que (1) da la impresión engañosa de que el código gira, cuando en realidad no lo hace. – MrZebra

+1

@shodanex: puede ser que el lenguaje no sea lo suficientemente expresivo como para expresar de forma nativa lo que quería hacer (es decir, una cláusula "final" como en java). entonces cualquier clase de truco para evitar esa limitación va a tener algunos inconvenientes asociados. – Chii

0

Si bien usar "goto" para situaciones de manejo de errores es bastante común, aún prefiero la solución "while" (o "do while"). En el caso "goto", hay muchas menos cosas que el compilador puede garantizar. Si crea un error tipográfico en el nombre de la etiqueta, el compilador no puede ayudarlo allí. Si alguien usa otro goto en otra etiqueta en ese bloque, hay buenas posibilidades de que no se llame al código de limpieza. Cuando utiliza las construcciones de control de flujo más estructuradas, siempre se garantiza qué código se ejecutará una vez que termine el ciclo.

+0

El compilador puede detectar errores tipográficos con facilidad: temp.c: 9: error: etiqueta 'blarg4h' utilizada pero no definida temp.c: 7: advertencia: etiqueta 'blargh' definida pero no utilizada – jmtd

7

Yo sé que mi estilo no es el más fresco posible, pero lo prefieren porque no necesita ningún construcciones especiales y es concisa y no demasiado difícil de entender:

 
error = (!error) && do_something1(); 
error = (!error) && do_something2(); 
error = (!error) && do_something3(); 

// Cleanup code 
+1

Terrible, pero clara. – Arafangion

+1

si 'do_something1()' devuelve '1' luego la segunda línea cambia' error' a '0' de nuevo y' do_something3() 'se ejecuta. Puede que no sea lo que quieres. – jfs

+0

Puede reemplazar el '=' por '+ =' ... –

2

utilizarlo si se puede 't utilizar Goto por cualquier razón

  • prohibidos en las convenciones de su proyecto
  • prohibidos bajo su herramienta de pelusa

También creo que es también uno de los casos en los que las macros no son malas:

#define DO_ONCE for (int _once_dummy = 0; _once_dummy < 1; _once_dummy++) 
+0

En C89, declarar una variable en un bucle no está permitido. Está permitido en C99. – jmtd

5

¿Por qué no utilizar una serie de declaraciones if? Yo suelo escribir de esta manera, ya que me parece mucho más claro que un bucle:

bool ok = true; 

do_something(); 
if (was_an_error()) ok = false; 

if (ok) 
{ 
    do_something_else(); 
    if (was_an_error()) ok = false; 
} 

if (ok) 
{ 
    do_something_else_again(); 
    if (was_an_error()) ok = false; 
} 

[...] 

[Cleanup code] 

también si se está trabajando con los estándares de codificación estricta, sí goto es probable que sea prohibido, pero a menudo lo son break y continue por lo el ciclo no es necesariamente una solución para eso.

+1

Eso se puede comprimir un poco: if (ok) {do_something(); ok = was_an_error();} –

+1

@Steve ¿no quieres decir ok =! was_an_error()? – finnw

+0

En un sistema en tiempo real esto podría tener una penalización de rendimiento. Estás haciendo varios saltos condicionales en lugar de solo uno. –

5

"break" comprende la semántica del alcance del bloque, mientras que "goto" no lo tiene en cuenta. En otras palabras, "while-break" puede traducirse a lenguajes funcionales como Lisp con recursividad de cola, "goto" no puede.

0

"hacer, mientras que" y "Ir a cabo" son diferentes en estas áreas:

1.inicialización de variable local

void foo(bool t = false) 
{ 
    if (t) 
    { 
     goto DONE; 
    } 

    int a = 10; // error : Goto bypass local variable's initialization 

    cout << "a=" << a << "\n"; 
DONE: 
} 

Está bien inicializar las variables locales in situ en do ... while (0) block.

void bar(bool t = false) 
{ 
    do{ 
     if (t) 
     { 
      break; 
     } 

     int a = 10; // fine 

     cout << "a=" << a << "\n"; 
    } while (0); 

} 

2 diferencia para Macros. "do while" es un poco mejor. "Goto HECHO" en una macro no es el caso. Si el código de salida es más complicado, por no vea así:

err = some_func(...); 
if (err) 
{ 
    register_err(err, __LINE__, __FUNC__); 
#if defined (_DEBUG) 
    do_some_debug(err) 
#endif 
    break; 
} 

y se escribe el código una y otra vez, es probable que los puso en una macro.

#define QUIT_IF(err)      \ 
if (err)          \ 
{            \ 
    register_err(err, __LINE__, __FUNC__);  \ 
    DO_SOME_DEBUG(err)       \ 
    break; // awful to put break in macro, but even worse to put "goto DONE" in macro. \ 
} 

Y el código se convierten en:

do 
{ 
    initial(); 

    do 
    { 
     err = do_step1(); 
     QUIT_IF(err); 

     err = do_step2(); 
     QUIT_IF(err); 

     err = do_step3(); 
     QUIT_IF(err); 

     .... 
    } while (0); 
    if (err) {  // harder for "goto DONE" to get here while still using macro. 
     err = do_something_else(); 
    } 
    QUIT_IF(err); 
    ..... 
} while (0); 

3.do ... while (0) maneja diferentes niveles de salida de la misma macro. El código se muestra arriba. goto ... no es el caso para Macro porque necesita etiquetas diferentes para diferentes niveles.

Al decir eso, no me gustan los dos. Prefiero usar el método de excepción. Si no se permite la excepción, entonces uso "do ... while (0)", dado que todo el bloque está sangrado, en realidad es más fácil de leer que el estilo "goto DONE".

Cuestiones relacionadas