2010-07-27 8 views
16

Supongamos que escribo mi código muy a la defensiva y siempre compruebo los tipos de devolución de todas las funciones que llamo.¿Cómo evitar cadena larga de libre (o elimina) después de cada comprobación de error en C?

Así que van como:

char* function() { 
    char* mem = get_memory(100); // first allocation 
    if (!mem) return NULL; 

    struct binder* b = get_binder('regular binder'); // second allocation 
    if (!b) { 
     free(mem); 
     return NULL; 
    } 

    struct file* f = mk_file(); // third allocation 
    if (!f) { 
     free(mem); 
     free_binder(b); 
     return NULL; 
    } 

    // ... 
} 

Aviso la rapidez consiguen free() cosas fuera de control. Si parte de la función falla, tengo que liberar cada asignación anterior. El código rápidamente se vuelve feo y todo lo que hago es copiar y pegar todo. Me convierto en un programador de copiar/pegar, y lo que es peor, si alguien agrega una declaración en algún punto intermedio, tiene que modificar todo el código a continuación para llamar al free() y agregarlo.

¿Cómo hacen los programadores de C experimentados este problema? No puedo entender nada.

Gracias, Boda Cydo.

Respuesta

34

Puede definir una nueva etiqueta que liberaría los recursos y luego podría GOTO cada vez que su código falle.

char* function() 
{ 
    char* retval = NULL; 
    char* mem = get_memory(100); // first allocation 
    if (!mem) 
     goto out_error; 

    struct binder* b = get_binder('regular binder'); // second allocation 
    if (!b) 
     goto out_error_mem; 

    struct file* f = mk_file(); // third allocation 
    if (!f) 
     goto out_error_b; 

    /* ... Normal code path ... */ 
    retval = good_value; 

    out_error_b: 
    free_binder(b); 
    out_error_mem: 
    free(mem); 
    out_error: 
    return retval; 
} 

Gestión de errores con GOTO ya se había discutido aquí: Valid use of goto for error management in C?

+6

+1 - aunque no es popular en la escuela, esto es lo más práctico y se hace todo el tiempo. ponga todas sus declaraciones libres en una etiqueta justo antes de abandonar la función, primero compruebe una variable de estado si es necesario, y use GOTO. –

+0

¿Cómo funciona eso? Nunca escuché acerca de esta técnica. ¿Puedes ilustrarlo por favor? – bodacydo

+0

Ahí tienes, sería algo así. – karlphillip

5

Sé que me van a linchado por esto, pero yo tenía un amigo que dijeron que habían utilizado goto para eso.

Luego me dijo que no era suficiente en la mayoría de los casos y que ahora usaba setjmp()/longjmp(). Básicamente reinventó las excepciones de C++ pero con mucha menos elegancia.

Dicho esto, ya que podría goto trabajo, usted podría refactorizar en algo que no utiliza goto, pero la sangría se vaya de las manos rápida:

char* function() { 
    char* result = NULL; 
    char* mem = get_memory(100); 
    if(mem) { 
     struct binder* b = get_binder('regular binder'); 
     if(b) { 
      struct file* f = mk_file(); 
      if (f) { 
       // ... 
      } 
      free(b); 
     } 
     free(mem); 
    } 
    return result; 
} 

Por cierto, la dispersión de la variable local declaraciones de todo el bloque de esa manera no es estándar C.

Ahora, si se da cuenta de que free(NULL); se define por la norma C como que no hace nada, puede simplificar la anidación alguna:

char* function() { 
    char* result = NULL; 

    char* mem = get_memory(100); 
    struct binder* b = get_binder('regular binder'); 
    struct file* f = mk_file(); 

    if (mem && b && f) { 
     // ... 
    } 

    free(f); 
    free(b); 
    free(mem); 

    return result; 
} 
+0

Aclaración: dispersar las declaraciones de variables locales alrededor del bloque, como en su código no es estándar C, pero mi primer ejemplo cumple con el estándar porque cada bloque respaldado tiene las declaraciones primero. –

+0

Creo que a partir de C99 esto ahora es estándar: http://en.wikipedia.org/wiki/C99#Design –

+0

C lo ha permitido durante 10 años. – dreamlax

5

Aunque admiro su enfoque a la codificación defensiva y eso es algo bueno. Y cada programador de C debe tener esa mentalidad, puede aplicarse a otros idiomas también ...

Tengo que decir que esto es lo único útil sobre GOTO, a pesar de que los puristas dirán lo contrario, eso sería equivalente a un Por último bloque, pero hay uno en particular que Gotcha puedo ver allí ...

código

karlphillip 's es casi completa, pero .... Supongamos que la función se hace de la siguiente

char* function() { 
     struct file* f = mk_file(); // third allocation 
     if (!f) goto release_resources; 
     // DO WHATEVER YOU HAVE TO DO.... 
     return some_ptr; 
    release_resources: 
     free(mem); 
     free_binder(b); 
     return NULL; 
    } 

Tenga cuidado! !! Esto dependerá del diseño y el propósito de la función que considere adecuada, que se deje de lado ... si regresara de la función de esa manera, podría terminar cayendo en la etiqueta release_resources ....lo que podría inducir una falla sutil, todas las referencias a los apuntadores en el montón se han ido y podrían terminar devolviendo basura ... así que asegúrese de que si tiene la memoria asignada y la devuelve, use la palabra clave return inmediatamente antes de la etiqueta la memoria podría desaparecer ... o crear una pérdida de memoria ....

+0

Acepto, tiendo a preferir mantener una variable de estado y tener todas las rutas a través de la función salir de la etiqueta, verificar la variable de estado para liberar cualquier asignación de memoria, o eliminar archivos temporales, etc. –

+0

@Brandon: Absolutamente - eso es una cosa sutil que puede tropezar ... y tu pasas lo que pasó ... "oooh maldita sea ... se me olvidó que se cayó a través de la etiqueta ... suspiro": D – t0mm13b

2

Si desea hacerlo sin goto, aquí es un enfoque que se adapta bien:

char *function(char *param) 
{ 
    int status = 0; // valid is 0, invalid is 1 
    char *result = NULL; 
    char *mem = NULL: 
    struct binder* b = NULL; 
    struct file* f = NULL: 

    // check function parameter(s) for validity 
    if (param == NULL) 
    { 
     status = 1; 
    } 

    if (status == 0) 
    { 
     mem = get_memory(100); // first allocation 

     if (!mem) 
     { 
      status = 1; 
     } 
    } 

    if (status == 0) 
    { 
     b = get_binder('regular binder'); // second allocation 

     if (!b) 
     { 
      status = 1; 
     } 
    } 

    if (status == 0) 
    { 
     f = mk_file(); // third allocation 

     if (!f) 
     { 
      status = 1; 
     } 
    } 

    if (status == 0) 
    { 
     // do some useful work 
     // assign value to result 
    } 

    // cleanup in reverse order 
    free(f); 
    free(b); 
    free(mem); 

    return result; 
} 
+6

Esto es mucho más horrible que cualquier goto podría ser . –

+0

@R: Eye of the beholder. Algunas personas consideran 'goto' un criterio para fallar una revisión del código. –

+6

Eso es porque algunas personas no saben cómo pensar críticamente. Ningún constructo es siempre peligroso. Algunos constructos son más propensos a causar problemas que otros. Uno debe considerar los costos y beneficios, en lugar de decir ciegamente "nunca usar X". – siride

2

Si sus estructuras de datos son complicados/anidado, un solo goto podría no ser suficiente, en cuyo caso sugiero algo como:

mystruct = malloc(sizeof *mystruct); 
if (!mystruct) goto fail1; 
mystruct->data = malloc(100); 
if (!mystruct->data) goto fail2; 
foo = malloc(sizeof *foo); 
if (!foo) goto fail2; 
... 
return mystruct; 
fail2: 
free(mystruct->data); 
fail1: 
free(mystruct); 

Un ejemplo del mundo real sería más complicado y podría implicar múltiples niveles de la estructura de anidación, listas enlazadas, etc. señalar aquí que free(mystruct->data); no puede ser llamado (derreferenciación un elemento de mystruct no es válido) si el primer malloc fallidos.

3

También puede tomar el camino contrario y comprobar para el éxito:

struct binder* b = get_binder('regular binder'); // second allocation 
if(b) { 
    struct ... *c = ... 
    if(c) { 
     ... 
    } 
    free(b); 
} 
+0

y tiene una variable de "resultados" para la devolución final. –

Cuestiones relacionadas