2011-09-20 11 views
5

dado una declaración de la función así:¿Cómo liberar memoria previamente asignada cuando ocurrieron errores?

int base_address(zval *object, int add_prefix, char **base_address TSRMLS_DC) {  
    int result; 

    char *host; 
    long port; 
    char *prefix; 

    host = ... get host from object ...; 
    port = ... get port from object ...; 
    prefix = ... get prefix from object ...; 

    result = SUCCESS; 

    if (asprintf(base_address, "%s:%ld/%s", host, port, prefix) < 0) { 
     result = FAILURE; 
    } 

    return result; 
} 

void my_func() { 
    char *base_address; 
    char *ping_url; 

    if (base_address(getThis(), 0, &base_address TSRMLS_CC) == FAILURE) { 
     MALLOC_ERROR(); 
    } 

    if (asprintf(&ping_url, "%s/ping", base_address) < 0) { 
     MALLOC_ERROR(); 
    } 

    ... do some stuff with base address ... 

    // release both, as everything worked 
    free(base_address); 
    free(ping_url); 
} 

Si la primera llamada a base_address tuvo éxito y la segunda llamada a asprintf() falló, ¿cómo limpiamente saltar al final de la función con el fin de liberar asignado de forma segura ¿memoria?

¿Hay algún patrón estándar para evitar fugas de memoria en estas situaciones en las que la memoria se asigna una tras otra (y cada asignación puede fallar) sin demasiada duplicación de código o declaraciones goto?

+5

Goto es su amigo aquí –

+0

¿Qué hay de malo en Goto? Podrías usar un constructo do while (0), pero eso es esencialmente lo mismo. – Luke

+0

¿Qué hay de RAII? – Dani

Respuesta

2

Si es NULL se asigna a las variables de puntero durante la declaración a continuación free será capaz de manejar los casos en los que nunca fueron malloc ed también (simplemente se hacer nada). (Se pueden usar otros guardias, tales como -1 para referirse a un fd no válido, por ejemplo.)

utilizo este en conjunción con con el uso goto para la limpieza (adicional) - algunas de las otras respuestas miro lejos para "decirme" a mí. Encuentro que goto tiene que usarse con prudencia para evitar el código de spaghetti y, en general, encuentro que "gotos alrededor de gotos" es demasiado difícil de seguir constantemente. La asignación adicional de NULL primero también permite que las variables mismas se usen como check-guard (durante cualquier posible limpieza o de otro modo).

Happy coding.


Ejemplo:

void my_func() { 
    char *base_address = NULL; 
    char *ping_url = NULL; 

    if (base_address(getThis(), 0, &base_address TSRMLS_CC) == FAILURE) { 
     goto cleanup; 
    } 

    if (asprintf(&ping_url, "%s/ping", base_address) < 0) { 
     goto cleanup; 
    } 

    // stuff... and assign return value (or return directly) if applicable, 
    // assign NULL to variables which contain values that should 
    // not be free'd, etc (use as guards!). 
    // I prefer to add guards into the cleanup vs. "skip over it". 
    // I vary rarely, if ever, have multiple cleanup sections -- in most 
    // cases this would indicate the need for additional function(s) to me. 

    cleanup: 
    free(base_address); 
    if (ping_url) { 
     // perhaps need additional cleanup   
     free(ping_url); 
    } 

    // Return value, if applicable. (If a "direct return" above isn't 
    // advisable for the given function due to cleanup rules.) 
} 
2

en C, que son bastante mucho por hacer si() con el fin de manejar el control de errores

en su caso

if (base_address(...)) 
{ 
    if (asprint(...)) 
    { 
    /// do some stuff 

    free(ping_url); 
    } 
    free(base_address); 
} 
7

Este es uno de los usos sanos de goto para el tratamiento de errores:

if (base_address(getThis(), 0, &base_address TSRMLS_CC) == FAILURE) { 
    goto end; 
} 

if (asprintf(&ping_url, "%s/ping", base_address) < 0) { 
    goto release_address; 
} 

// do stuff 

release_address: 
free(base_address); 
end: 

de esta manera usted no tiene que repetir el mismo código de liberación en caso de tener muchas llamadas asignación que dependen el uno del otro.

Es posible que desee consultar otra de mis respuestas here, que habla sobre el caso general.

+2

Si alguien lo vota por causa de goto, directamente puede hacer algo que no programa. –

9

No tengas miedo de goto. Es la manera más simple, más limpia y más legible de manejar excepciones en C:

  • No se repite. El código duplicado es propenso a errores.

  • No crea código profundamente anidado. La anidación profunda es ilegible.

  • No se esconde detrás de do {...} while (0) y break. El buen código dice lo que significa.

Aquí hay un ejemplo básico:

int operation() { 

    int result = SUCCESS; 

    if ((result = may_fail_first()) == FAILURE) { 
     goto failed_first; 
    } 

    if ((result = may_fail_second()) == FAILURE) { 
     goto failed_second; 
    } 

    // If your cleanup code doesn't ordinarily need to run. 
    goto end; 

failed_second: 
    cleanup_second(); 

    // If you don't need to clean up everything. 
    goto end; 

failed_first: 
    cleanup_first(); 

end: 
    return result; 

} 
Cuestiones relacionadas