Si dovrebbe verificare ogni piccolo errore in C? [duplicare]

58

Come buon programmatore si dovrebbero scrivere codici robusti che gestiranno ogni singolo risultato del suo programma. Tuttavia, quasi tutte le funzioni della libreria C restituiscono 0 o -1 o NULL in caso di errore.

A volte è ovvio che è necessario il controllo degli errori, ad esempio quando si tenta di aprire un file. Ma spesso ignoro il controllo degli errori in funzioni come printf o anche malloc perché non mi sento necessario.

if(fprintf(stderr, "%s", errMsg) < 0){
    perror("An error occurred while displaying the previous error.");
    exit(1);
}

È buona norma ignorare determinati errori oppure esiste un modo migliore per gestire tutti gli errori?

    
posta Derek 朕會功夫 17.11.2015 - 00:59
fonte

7 risposte

27

In generale, il codice dovrebbe gestire condizioni eccezionali dovunque sia appropriato. Sì, questa è una dichiarazione vaga.

Nei linguaggi di livello superiore con la gestione delle eccezioni del software, questo è spesso indicato come "cattura l'eccezione nel metodo in cui puoi effettivamente fare qualcosa al riguardo". Se si è verificato un errore nel file, è possibile che si lasci sfuggire lo stack al codice dell'interfaccia utente che può effettivamente dire all'utente "il file non è riuscito a salvare su disco". Il meccanismo di eccezione in pratica inghiotte "ogni piccolo errore" e lo gestisce implicitamente nel punto appropriato.

In C, non hai quel lusso. Esistono alcuni modi per gestire gli errori, alcuni dei quali sono funzionalità di linguaggio / libreria, alcune delle quali sono pratiche di codifica.

Is it a good practice to just ignore certain errors, or is there a better way to handle all the errors?

Ignora determinati errori? Può essere. Ad esempio, è ragionevole presumere che la scrittura sullo standard output non fallirà. Se fallisce, come lo diresti comunque all'utente? Sì, è una buona idea ignorare determinati errori o il codice in modo difensivo per prevenirli. Ad esempio, controlla zero prima di dividerlo.

Ci sono modi per gestire tutti gli errori, o almeno la maggior parte:

  1. È possibile utilizzare salti, simili a gotos, per la gestione degli errori . Pur trattandosi di un problema conflittuale tra i professionisti del software, vi sono per loro validi usi, specialmente nel codice integrato e critico per le prestazioni (ad esempio il kernel Linux).
  1. Cascading if s:

    if (!<something>) {
      printf("oh no 1!");
      return;
    }
    if (!<something else>) {
      printf("oh no 2!");
      return;
    }
    
  2. Verifica la prima condizione, ad es. aprire o creare un file, quindi assumere le operazioni successive.

Il codice robusto è buono e si dovrebbe controllare e gestire gli errori. Il metodo migliore per il tuo codice dipende da cosa fa il codice, da quanto è critico un errore, ecc. E solo tu puoi veramente rispondere. Tuttavia, questi metodi sono testati in battaglia e utilizzati in vari progetti open source in cui è possibile dare un'occhiata per vedere come il codice reale verifica la presenza di errori.

    
risposta data 17.11.2015 - 02:22
fonte
15

However, almost all functions from the C library will return 0 or -1 or NULL when there's an error.

Sì, ma conosci quale funzione hai chiamato, vero?

In realtà ci sono molte informazioni che potresti inserire in un messaggio di errore. Sapete quale funzione è stata chiamata, il nome della funzione che lo ha chiamato, quali parametri sono stati passati e il valore di ritorno della funzione. Sono molte informazioni per un messaggio di errore molto informativo.

Non devi farlo per ogni chiamata di funzione. Ma la prima volta che visualizzi il messaggio di errore "Si è verificato un errore durante la visualizzazione dell'errore precedente", quando ciò che realmente ti serviva erano informazioni utili, sarà l'ultima volta che vedrai il messaggio di errore lì, perché cambierai immediatamente il messaggio di errore a qualcosa di informativo che ti aiuterà a risolvere il problema.

    
risposta data 17.11.2015 - 01:18
fonte
10

TLDR; non dovresti quasi mai ignorare gli errori.

Il linguaggio C manca di una buona funzionalità di gestione degli errori, lasciando a ogni sviluppatore della libreria l'implementazione delle proprie soluzioni. I linguaggi più moderni hanno eccezioni costruite in cui questo particolare problema è molto più facile da gestire.

Ma quando sei bloccato con C non hai tali vantaggi. Purtroppo dovrai semplicemente pagare il prezzo ogni volta che chiami una funzione che presenta una remota possibilità di fallimento. Oppure subirai conseguenze molto peggiori, come la sovrascrittura involontaria dei dati nella memoria. Quindi come regola generale devi controllare sempre gli errori.

Se non controlli il rendimento di fprintf , molto probabilmente lascerai un bug che, nel migliore dei casi, non farà ciò che l'utente si aspetta e il caso peggiore esploderà durante il volo. Non ci sono scuse per minare te stesso in questo modo.

Tuttavia, come sviluppatore C, è anche il tuo lavoro rendere il codice facile da mantenere. Quindi a volte puoi tranquillamente ignorare gli errori per maggiore chiarezza se (e solo se) non rappresentano una minaccia per il comportamento generale dell'applicazione. .

È lo stesso problema di questo:

try
{
    run();
} catch (Exception) {
    // comments expected here!!!
}

Se si vede che senza buoni commenti all'interno del blocco catch vuoto questo è certamente un problema. Non c'è motivo di pensare che una chiamata a malloc() venga eseguita correttamente il 100% delle volte.

    
risposta data 17.11.2015 - 02:38
fonte
9

La domanda non è in realtà specifica della lingua, ma piuttosto specifica dell'utente. Pensa alla domanda dal punto di vista dell'utente. L'utente fa qualcosa, come digitare il nome del programma su una riga di comando e premere Invio. Cosa si aspetta l'utente? Come possono sapere se qualcosa è andato storto? Possono permettersi di intercedere se si verifica un errore?

In molti tipi di codice, tali controlli sono eccessivi. Tuttavia, in un codice critico di sicurezza ad alta affidabilità, come quelli per i reattori nucleari, il controllo degli errori patologici e i percorsi di recupero pianificati fanno parte della natura quotidiana del lavoro. Si ritiene che valga la pena dedicare del tempo a chiedere "Cosa succede se X fallisce? Come posso tornare in uno stato sicuro?" In un codice meno affidabile, come quelli dei videogiochi, puoi farla franca con un controllo degli errori molto inferiore.

Un altro approccio simile è quanto puoi effettivamente migliorare sullo stato rilevando l'errore? Non riesco a contare il numero di programmi C ++ che catturano con orgoglio le eccezioni, solo per ricrearli perché non sapevano veramente cosa fare con loro ... ma sapevano che avrebbero dovuto gestire le eccezioni. Tali programmi non hanno guadagnato nulla dallo sforzo extra. Aggiungete solo il codice di controllo degli errori che pensate possa effettivamente gestire la situazione meglio che semplicemente non controllando il codice di errore. Detto questo, C ++ ha regole specifiche per la gestione delle eccezioni che si verificano durante la gestione delle eccezioni al fine di catturarle in modo più significativo (e con questo intendo chiamare terminate () per assicurarsi che la pira funebre che hai costruito per te si illumini nella sua giusta gloria)

Quanto patologico puoi ottenere? Ho lavorato su un programma che definiva una "SafetyBool" i cui valori veri e falsi erano scelti con cura per avere una distribuzione uniforme di 1 e 0, e sono stati scelti in modo tale che uno qualsiasi dei numerosi errori hardware (bit bit flip, bus dati) tracce che si rompono, ecc.) non ha causato un'interpretazione errata di un booleano. Inutile dire che non direi che si tratta di una pratica di programmazione generica da utilizzare in qualsiasi vecchio programma.

    
risposta data 17.11.2015 - 05:20
fonte
6
  • Diversi requisiti di sicurezza richiedono diversi livelli di correttezza. Nel software di controllo aereo o automobilistico verranno verificati tutti i valori di ritorno, cfr. MISRA.FUNC.UNUSEDRET . In una rapida dimostrazione del concetto che non lascia mai la tua macchina, probabilmente no.
  • La codifica costa tempo. Troppi controlli irrilevanti in software non sicuro per la sicurezza è uno sforzo che è meglio spendere altrove. Ma dov'è il punto debole tra qualità e costi? Ciò dipende dagli strumenti di debug e dalla complessità del software.
  • La gestione degli errori può oscurare il flusso di controllo e introdurre nuovi errori. Mi piace molto Richard "network" le funzioni wrapper di Stevens che riportano almeno errori.
  • La gestione degli errori può, raramente, essere un problema di prestazioni. Ma la maggior parte delle chiamate alle librerie C richiederà così tanto tempo che il costo di controllare un valore di ritorno è infinitamente piccolo.
risposta data 17.11.2015 - 14:50
fonte
3

Un po 'di riflessione astratta sulla domanda. E non è necessariamente per il linguaggio C.

Per i programmi più grandi avresti un livello di astrazione; forse una parte del motore, una biblioteca o all'interno di un quadro. A quel livello non interesserebbe il tempo in cui si ottiene un dato valido o l'output sarebbe un valore predefinito: 0 , -1 , null ecc.

Poi c'è un livello che sarebbe la tua interfaccia per il livello astratto, che farebbe molta gestione degli errori e forse altre cose come le iniezioni di dipendenza, l'ascolto degli eventi ecc.

E più tardi avresti il tuo livello di implementazione concreto in cui imposti effettivamente le regole e gestisci l'output.

Quindi la mia opinione è che a volte è meglio escludere completamente la gestione degli errori da una parte del codice perché quella parte semplicemente non fa quel lavoro. E poi avere un processore che valuterà l'output e indicherà un errore.

Questo è fatto principalmente per separare le responsabilità che portano alla leggibilità del codice e ad una migliore scalabilità.

    
risposta data 17.11.2015 - 03:55
fonte
0

In generale, a meno che tu non abbia una buona ragione per non che controlla la condizione di errore, dovresti. L'unica buona ragione per cui non riesco a trovare una condizione di errore è quando non puoi fare qualcosa di significativo se fallisce. Questa è una barra molto difficile da incontrare, perché c'è sempre un'opzione praticabile: uscire con garbo.

    
risposta data 17.11.2015 - 16:23
fonte

Leggi altre domande sui tag