2009-03-31 14 views
6

Sí, dos constructos odiados combinados. ¿Es tan malo como suena o puede verse como una buena forma de controlar el uso de goto y también proporciona una estrategia de limpieza razonable?Macros de control de flujo con 'goto'

En el trabajo, tuvimos una discusión sobre si permitimos ir a nuestro estándar de codificación. En general, nadie quería permitir el uso gratuito de goto, pero algunos eran positivos sobre su uso para saltos de limpieza. Al igual que en este código:

void func() 
{ 
    char* p1 = malloc(16); 
    if(!p1) 
     goto cleanup; 

    char* p2 = malloc(16); 
    if(!p2) 
     goto cleanup; 

goto norm_cleanup; 

err_cleanup: 

    if(p1) 
     free(p1); 

    if(p2) 
     free(p2); 

norm_cleanup: 
} 

El beneficio abovious de tal uso es que usted no tiene que terminar con este código:

void func() 
{ 
    char* p1 = malloc(16); 
    if(!p1){ 
     return; 
    } 

    char* p2 = malloc(16); 
    if(!p2){ 
     free(p1); 
     return; 
    } 

    char* p3 = malloc(16); 
    if(!p3){ 
     free(p1); 
     free(p2); 
     return; 
    } 
} 

Especialmente en funciones constructoras como con muchas asignaciones esto puede a veces crecen muy mal, no menos cuando alguien tiene que insertar algo en el medio.

Por lo tanto, para poder utilizar goto, pero aún así aislarlo claramente de ser utilizado libremente, se creó un conjunto de macros de control de flujo para manejar la tarea. Se ve algo como esto (simplificado):

#define FAIL_SECTION_BEGIN int exit_code[GUID] = 0; 
#define FAIL_SECTION_DO_EXIT_IF(cond, exitcode) if(cond){exit_code[GUID] = exitcode; goto exit_label[GUID];} 
#define FAIL_SECTION_ERROR_EXIT(code) exit_label[GUID]: if(exit_code[GUID]) int code = exit_code[GUID];else goto end_label[GUID] 
#define FAIL_SECTION_END end_label[GUID]: 

podemos utilizar esto como sigue:

int func() 
{ 
    char* p1 = NULL; 
    char* p2 = NULL; 
    char* p3 = NULL; 

    FAIL_SECTION_BEGIN 
    { 
     p1 = malloc(16); 
     FAIL_SECTION_DO_EXIT_IF(!p1, -1); 

     p2 = malloc(16); 
     FAIL_SECTION_DO_EXIT_IF(!p2, -1); 

     p3 = malloc(16); 
     FAIL_SECTION_DO_EXIT_IF(!p3, -1); 
    } 
    FAIL_SECTION_ERROR_EXIT(code) 
    { 
     if(p3) 
     free(p3); 

     if(p2) 
     free(p2); 

     if(p1) 
     free(p1); 

     return code; 
    } 
    FAIL_SECTION_END 

    return 0; 

Se ve bien, y viene con muchos beneficios, pero, ¿existen inconvenientes que deben estar pensando antes de implementar esto en el desarrollo? Después de todo, es muy controlador de flujo y goto: ish. Ambos están desalentados. ¿Cuáles son los argumentos para desalentarlos en este caso?

Gracias.

+0

Como Martin Fido señala en su respuesta, en el último fragmento de código, los punteros p1, p2 y p3 están fuera de la sección de manejo de errores, y en el primer fragmento de código p2 contendrá datos de basura (si compila en absoluto, no estoy seguro acerca de las reglas de C). –

+0

@j: estoy seguro de que este código ni siquiera pasará por el compilador. Gracias por la sugerencia, pero debe haber perdido todo el sentido de la pregunta. – sharkin

+0

@ R.A: ¿Qué te hace pensar que me perdí el punto? La falta de complicidad fue un tema secundario confuso que pensé que valía la pena señalar en un comentario. –

Respuesta

11

gestión de errores es una de las raras ocasiones cuando es goto no es tan malo.

Pero si tuviera que mantener ese código me molestaría mucho que goto estén ocultos por las macros.

Por lo tanto, en este caso goto está bien para mí, pero no macros.

+0

el manejo de errores no debería ser una situación rara: P – quinmars

+0

la rara situación no es "manejo de errores", es "uso ir". – mouviciel

+0

No me importa usar goto dentro de una macro y hacerlo de vez en cuando, PERO ocultar la etiqueta saltada dentro de la macro es un absoluto no-no. Es decir. no está bien: "#define CHECK_RESULT (res) if (res == 0) goto cleanup;", ok: "#define CHECK_RESULT (res, label) if (res == 0) goto label;". De esta forma, puede comprender que los saltos de macro (por ejemplo, "res = something(); CHECK_RESULT (res, cleanup); ... cleanup: ..."). – hlovdal

7

El uso de goto para ir a un error común en la secuencia del controlador/limpieza/salida es absolutamente correcto.

+0

Sin embargo, omitiría las macros. Los ojos de su programador están capacitados para if() s. Esas macros aparecen comparativamente raramente, y por lo tanto son mucho más difíciles de leer. –

7

este código:

void func() 
{ 
    char* p1 = malloc(16); 
    if(!p1) 
     goto cleanup; 

    char* p2 = malloc(16); 
    if(!p2) 
     goto cleanup; 

cleanup: 

    if(p1) 
     free(p1); 

    if(p2) 
     free(p2); 
} 

puede escribirse legalmente como:

void func() 
{ 
    char* p1 = malloc(16); 
    char* p2 = malloc(16); 

    free(p1); 
    free(p2); 
} 

si las asignaciones de memoria éxito.

Esto funciona porque free() no hace nada si pasa un puntero NULL. Puede utilizar el mismo lenguaje en el diseño de sus propias APIs para asignar y otros recursos gratuitos:

// return handle to new Foo resource, or 0 if allocation failed 
FOO_HANDLE AllocFoo(); 

// release Foo indicated by handle, - do nothing if handle is 0 
void ReleaseFoo(FOO_HANDLE h); 

API Diseñando como este puede simplificar considerablemente la gestión de recursos.

+0

Sí, lo siento, olvidé un código. Actualizó la pregunta. – sharkin

+0

Sin embargo, es específico de malloc, ¿qué hay de llamar a otras funciones de asignación de recursos? No lo sabía, sin embargo, gracias – shodanex

+0

Incorrecto, ese código es C99 específico. – sharkin

1

El primer ejemplo parece mucho más legible para mí que la versión macro. Y mouviciel dijo que es mucho mejor que yo

2

El término "programación estructurada" que todos conocemos como la cosa anti-Goto originalmente iniciado y desarrollado como un montón de codificación de los patrones con los de Goto (o JMP 's). Esos patrones se llamaron los patrones while y if, entre otros.

Por lo tanto, si está utilizando goto, úselos de forma estructurada. Eso limita el daño. Y esos macro parecen un enfoque razonable.

3

La limpieza con goto es una expresión en C común y es used in Linux kernel *.

** Tal opinión de Linus no es el mejor ejemplo de un buen argumento, pero sí muestra goto ser utilizado en un proyecto de escala relativamente grande. *

3

Si falla el primer malloc, limpie ambos p1 y p2. Debido a goto, p2 no se inicializa y puede apuntar a cualquier cosa. Ejecuté esto rápidamente con gcc para verificar e intentar liberar (p2) de hecho causaría un fallo seg.

En su último ejemplo, las variables se delimitan dentro de las llaves (es decir, solo existen en el bloque FAIL_SECTION_BEGIN).

Suponiendo que el código funciona sin llaves, aún tendría que inicializar todos los punteros a NULL antes de FAIL_SECTION_BEGIN para evitar fallas seg.

tengo nada en contra de Goto y macros, pero yo prefiero la idea de Neil Butterworth ..

void func(void) 
{ 
    void *p1 = malloc(16); 
    void *p2 = malloc(16); 
    void *p3 = malloc(16); 

    if (!p1 || !p2 || !p3) goto cleanup; 

    /* ... */ 

cleanup: 
    if (p1) free(p1); 
    if (p2) free(p2); 
    if (p3) free(p3); 
} 

O si es más apropiado ..

void func(void) 
{ 
    void *p1 = NULL; 
    void *p2 = NULL; 
    void *p3 = NULL; 

    p1 = malloc(16); 
    if (!p1) goto cleanup; 

    p2 = malloc(16); 
    if (!p2) goto cleanup; 

    p3 = malloc(16); 
    if (!p3) goto cleanup; 

    /* ... */ 

cleanup: 
    if (p1) free(p1); 
    if (p2) free(p2); 
    if (p3) free(p3); 
} 
+0

+1. Buen punto para jugar con variables que "todavía no existen" en la sección de limpieza. –

+0

Naturalmente, es un error tipográfico, y obviamente no he intentado compilarlo (los punteros serían inutilizables en la sección de salida). Obviamente, tales detalles realmente no son la esencia de la cuestión que nos ocupa. – sharkin

+0

@ R.A: No, no son la esencia, pero oscurecen el punto que intentas hacer.Cuando publica fragmentos de código, es simple cortesía asegurarse de que compilan y funcionan según lo previsto. –

2

El código original podría beneficiarse del uso de múltiples sentencias de retorno - no hay necesidad de saltar alrededor del código de limpieza de retorno de error. Además, normalmente necesita el espacio asignado liberado en una devolución normal; de lo contrario, tendrá pérdidas de memoria. Y puede volver a escribir el ejemplo sin goto si tiene cuidado. Este es un caso en el que se puede declarar de manera útil variables antes de lo contrario es necesario:

void func() 
{ 
    char *p1 = 0; 
    char *p2 = 0; 
    char *p3 = 0; 

    if ((p1 = malloc(16)) != 0 && 
     (p2 = malloc(16)) != 0 && 
     (p3 = malloc(16)) != 0) 
    { 
     // Use p1, p2, p3 ... 
    } 
    free(p1); 
    free(p2); 
    free(p3); 
} 

Cuando hay cantidades no triviales de trabajo después de cada operación de asignación, entonces se puede utilizar una etiqueta antes de la primera de las free() operaciones, y a goto está bien - el manejo de errores es la razón principal para usar goto en estos días, y cualquier otra cosa es algo dudosa.

Cuido un código que tiene macros con instrucciones goto incrustadas. En el primer encuentro, es confuso ver una etiqueta que no está referenciada por el código visible, pero que no puede eliminarse. Prefiero evitar tales prácticas. Las macros están bien cuando no necesito saber lo que hacen, simplemente lo hacen. Las macros no son tan correctas cuando hay que saber para qué se expanden para usarlas con precisión. Si no me ocultan información, son más una molestia que una ayuda.

Ilustración - nombres disfrazados para proteger a los culpables:

#define rerrcheck if (currval != &localval && globvar->currtub &&   \ 
        globvar->currtub->te_flags & TE_ABORT)     \ 
        { if (globvar->currtub->te_state)      \ 
         globvar->currtub->te_state->ts_flags |= TS_FAILED;\ 
         else             \ 
         delete_tub_name(globvar->currtub->te_name);  \ 
         goto failure;          \ 
        } 


#define rgetunsigned(b) {if (_iincnt>=2) \ 
          {_iinptr+=2;_iincnt-=2;b = ldunsigned(_iinptr-2);} \ 
         else {b = _igetunsigned(); rerrcheck}} 

Hay varias docenas de variantes en rgetunsigned() que son algo similar - diferentes tamaños y diferentes funciones de la grúa.

Un lugar donde éstos se utilizan contiene este bucle - en un bloque más grande de código en un solo caso de un gran conmutador con algunos pequeños y algunos grandes bloques de código (no particularmente bien estructurado):

 for (i = 0 ; i < no_of_rows; i++) 
      { 
      row_t *tmprow = &val->v_coll.cl_typeinfo->clt_rows[i]; 

      rgetint(tmprow->seqno); 
      rgetint(tmprow->level_no); 
      rgetint(tmprow->parent_no); 
      rgetint(tmprow->fieldnmlen); 
      rgetpbuf(tmprow->fieldname, IDENTSIZE); 
      rgetint(tmprow->field_no); 
      rgetint(tmprow->type); 
      rgetint(tmprow->length); 
      rgetlong(tmprow->xid); 
      rgetint(tmprow->flags); 
      rgetint(tmprow->xtype_nm_len); 
      rgetpbuf(tmprow->xtype_name, IDENTSIZE); 
      rgetint(tmprow->xtype_owner_len); 
      rgetpbuf(tmprow->xtype_owner_name, IDENTSIZE); 
      rgetpbuf(tmprow->xtype_owner_name, 
        tmprow->xtype_owner_len); 
      rgetint(tmprow->alignment); 
      rgetlong(tmprow->sourcetype); 
      } 

¡No es obvio que el código está enlazado con declaraciones goto! Y claramente, una exégesis completa de los pecados del código del que proviene tomaría todo el día; son muchos y variados.

0
#define malloc_or_die(size) if(malloc(size) == NULL) exit(1) 

No es como realmente se puede recuperar de fallidos de malloc a menos que tenga el software vale la pena escribir un sistema de transacciones para, si lo haces, agrega Hacer retroceder el código para malloc_or_die.

Para obtener un ejemplo real del buen uso de goto, consulte el código de despacho de análisis que usa goto calculado.

+0

Desearía que pudieras publicar un enlace para obtener más información sobre "computed goto". Suena interesante. -1 para su opinión sobre la falla de asignación de memoria. – sharkin

+0

del desarrollador de pulseaudio: "En los sistemas modernos, ha quedado bastante claro que para el software de espacio de usuario normal, solo un malloc abortado() tiene sentido [...]" http://article.gmane.org/gmane.comp.audio. Jackit/19998 – CesarB

Cuestiones relacionadas