2011-09-20 14 views
5

Data una dichiarazione di funzione come questa:Come liberare memoria allocata in precedenza in caso di errori?

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); 
} 

Se la prima chiamata a base_address riuscito e la seconda chiamata a asprintf() non è riuscito, come faccio in modo pulito saltare alla fine della funzione al fine di rilasciare in modo sicuro allocato memoria?

C'è qualche schema standard su come evitare perdite di memoria in queste situazioni in cui la memoria viene assegnata una dopo l'altra (e ogni allocazione potrebbe fallire) senza troppe duplicazioni di codice o istruzioni goto?

+5

goto è tuo amico qui –

+0

Cosa c'è di sbagliato con goto? Potresti usare un do do while (0), ma fondamentalmente è la stessa cosa. – Luke

+0

Che mi dici di RAII? – Dani

risposta

2

Se NULL è assegnato alle variabili puntatore durante la dichiarazione poi free sarà in grado di gestire i casi in cui non sono mai stati malloc ed oltre (lo farà semplicemente fare niente). (Altre guardie può essere utilizzato, ad esempio -1 far riferimento ad un fd non valida, per esempio.)

Io uso questa in concomitanza con con l'uso goto per (aggiuntivo) pulizia - alcune delle altre risposte guarda lontano a "verboso" per me. Trovo che goto debba essere usato con giudizio per evitare il codice spaghetti e, in generale, trovo "gotos around gotos" troppo difficile per tenere traccia di consistentemente. L'assegnazione aggiuntiva di NULL consente inoltre alle variabili stesse di essere utilizzate come check-guard (durante ogni possibile pulizia o altro).

Felice codifica.


Esempio:

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

in C, si sono praticamente resta da fare se() al fine di gestire la gestione degli errori

nel tuo caso

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

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

Questo è uno degli usi sani di goto per la gestione degli errori:

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: 

in questo modo non c'è bisogno di ripetere lo stesso codice di rilascio nel caso in cui si dispone di molte chiamate assegnazione che dipendono l'uno dall'altro.

Si consiglia di fare riferimento ad un'altra delle mie risposte here, che parla del caso generale.

+2

Se qualcuno down vota a causa di goto, può direttamente fare qualcosa che non è programmabile. –

9

Non aver paura di goto. E 'il modo più semplice, pulito, e la maggior parte leggibile della gestione delle eccezioni in C:

  • Non ripetere te stesso. Il codice duplicato è soggetto a errori.

  • Non si crea codice profondamente annidato. Il nidificazione profonda è illeggibile.

  • Non ti nascondi dietro do {...} while (0) e break. Un buon codice dice cosa significa.

Ecco un esempio di base:

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; 

} 
Problemi correlati