Gestione degli errori in C con "check-log-return": perché non utilizzare una macro?

4

È una buona pratica (credo) in C per gestire errori come questo:

int status = tree_climb(tree, ...);
if (status != 0) {
    global_logger.message(2, "Cannot climb a tree %s", tree->name);
    return EPIPE;
}

o, in alternativa

forest_errno = tree_climb(tree, ...);
if (forest_errno != 0) {
    goto exit;
}

exit:
if (tree) {
    tree_dispose(tree);
}
if (forest) {
    forest_dispose(forest);
}

return forest_errno;

Le domande sono:

Quali sono i motivi per non utilizzare una macro per il preprocessore per questo? Non ricordo di aver visto un sacco di codice come:

#define CHECK_OR_RETURN(contract, error_status, log_level, message_format, ...) \
if (!(contract)) { \
    global_logger.message(log_level, message_format, ##__VA_ARGS__); \
    return error_status; \
}

Vedo questi motivi per utilizzare una macro:

  • Questo comprime 4 righe di codice, 3 delle quali non fanno nulla durante il normale flusso di operazioni, in 1.
  • La gestione degli errori, idealmente, non dovrebbe inquinare il codice per un flusso normale.

Vedo questi motivi per non utilizzare una macro:

    La macro
  • ottenerà la lettura e il debug del codice in qualche modo. La sintassi "magica" auto-inventata può trasformare un linguaggio familiare in un incomprensibile: a volte, non è possibile analizzare un codice senza conoscere le specifiche di una libreria. Sebbene, su larga scala, un codice 3 volte più breve sia 9 volte più leggibile.
  • In C ++, potremmo usare le eccezioni. Preferirei non utilizzare eccezioni non controllate, ma non rientrano nell'ambito della mia domanda.
  • Alcune persone semplicemente non si preoccupano della lunghezza del codice.

Quindi perché la gente non lo fa sempre?

    
posta Victor Sergienko 11.11.2016 - 19:01
fonte

3 risposte

4

La tua "alternativa" mostra che il tuo codice C potrebbe implementare un'acquisizione di risorse .

Prendi il seguente esempio, senza alcun errore di elaborazione, e il rientro appena usato qui per mostrare i diversi passaggi:

 FILE *fp = fopen("data.txt", "rb");          // step 1: acquire file resource
   Forest* forest = generate_forest(fp,...);    // step 2: allocate tree resource 
     Tree *tree = get_tree(forest, "SPAIN"...);   
     Element *element = malloc(sizeof(element));  // step 3: allocate new element
       element->value = MAGIC; 
       tree_add(tree, element);                 // suppose element is copied into the tree
     free (element);                          // end step 3 
     ...
   release_forest (forest);                 // end step 2 (forest takes care of its trees)
   ...
 fclose (fp);                             // end step 1

Qui può esserci un errore causato da ogni singola istruzione. A seconda del passaggio in cui si verifica l'errore, potrebbe essere necessario rilasciare alcune o alcune risorse in più.

Alternativa 1

L'uso della tua macro per registrare ed elaborare i diversi errori causerebbe solo perdite di risorse, restituendo whle non tutte le risorse sono state rilasciate.

Alternativa 2

Uso di alcune istruzioni condizionali annidate:

 FILE *fp = fopen("data.txt", "rb");          // step 1: acquire file resource
 if (fp) {                                    // go on for step 1
     Forest* forest = generate_forest(fp,...);    // step 2: allocate tree resource 
     if (forest) {                                // go on for step 2
        Tree *tree = get_tree(forest, "SPAIN"...);   
        ...
        release_forest (forest);                  // end step 2 (forest takes  
     } 
     else {
        errcode = logmyerror ("forest not properly loaded", ENOTFOUND);
     }
     fclose (fp);                             // end step 1
 }
 else {
     errcode = logmyerror ("file not found ", EOOPSAGAIN);
 }
 return errcode;   // final exit point 

Alternativa 3

Il goto ignoto (beh, ha alcuni vantaggi come vedrai), dove diverse etichette corrispondono a punti di uscita per i passaggi pertinenti:

 FILE *fp = fopen("data.txt", "rb");          // step 1: acquire file resource
 if (fp==NULL) {
     errcode = logmyerror ("file not found ", EOOPSAGAIN);
     goto out0; 
 }
 Forest* forest = generate_forest(fp,...);    // step 2: allocate tree resource 
 if (forest==NULL) { 
     errcode = logmyerror ("forest not properly loaded", ENOTFOUND);
     goto out1; 
 }  
 Tree *tree = get_tree(forest, "SPAIN"...);   
 ... 
out2: 
 release_forest (forest);                 // end step 2 (forest takes care of its trees)
   ...
out1: 
 fclose (fp);                             // end step 1

out0: 
 return errcode; 

Conclusione

Ovviamente, se non si dispone di un'allocazione di risorse multilivello, è possibile utilizzare perfettamente la macro. Ma temo che ci siano molte situazioni di vita reale in cui ciò non sarà sufficiente.

Nota che in C ++ non avresti bisogno di questo se usi RAII, i distruttori puliranno sempre le risorse ogni volta che torni.

    
risposta data 11.11.2016 - 19:32
fonte
2

Le persone non lo fanno per i motivi che hai elencato. Inoltre non esiste una comune "buona prassi di codifica", ma nel migliore dei casi si hanno alcune regole dipartimentali. Inoltre trovo un sacco di codice che non è coerente. Cioè, le parti usano i macro e le parti no. Dipende probabilmente da quale programmatore ha avuto le mani.

Quindi quale consiglio posso dare? Se hai molti di questi semplici snippet di gestione degli errori, sentiti libero di usare una macro. Penso che il codice denso sia meglio leggere che sparpagliato, specialmente con quei frammenti ripetitivi.

Ovviamente, se hai una gestione degli errori più complessa, potresti finire con macro ingestibili.

    
risposta data 11.11.2016 - 20:08
fonte
0

Se ti mancano eccezioni in C, puoi emularle con "salti lunghi". In questo modo, puoi avere un punto per gestire le eccezioni, come con try / catch, e questo punto può persino trovarsi su un livello di stack più alto.

Ecco un'implementazione di esempio, ma la base (longjmp / setjmp) faceva parte di C dall'inizio e non è necessario alcun software aggiuntivo. link

    
risposta data 13.11.2016 - 16:26
fonte

Leggi altre domande sui tag