Un programma C ++ dovrebbe catturare tutte le eccezioni e impedire che le eccezioni si diffondano oltre main ()?


29

Una volta mi è stato consigliato che un programma C ++ dovrebbe alla fine catturare tutte le eccezioni. Il ragionamento fornito all'epoca era essenzialmente che i programmi che consentivano alle eccezioni di emergere fuori da main()uno strano stato di zombi. Mi è stato detto questo diversi anni fa e, a posteriori, credo che il fenomeno osservato fosse dovuto alla lunga generazione di discariche core eccezionalmente grandi dal progetto in questione.

All'epoca sembrava bizzarro ma convincente. Era del tutto assurdo che il C ++ "punisse" i programmatori per non aver colto tutte le eccezioni, ma le prove che avevo davanti sembravano confermarlo. Per il progetto in questione, i programmi che generavano eccezioni non rilevate sembravano entrare in uno strano stato di zombi - o come sospetto che la causa fosse ora, un processo nel mezzo di una discarica di core indesiderata è insolitamente difficile da fermare.

(Per chiunque si chiedesse perché questo non fosse più ovvio al momento: il progetto ha generato una grande quantità di output in più file da più processi che hanno effettivamente oscurato qualsiasi tipo di aborted (core dumped)messaggio e in questo caso particolare, l'esame post mortem dei dump core non era si tratta di un'importante tecnica di debug, quindi non è stata data molta importanza ai dump principali. I problemi con un programma di solito non dipendevano dallo stato accumulato da molti eventi nel tempo da un programma di lunga durata ma piuttosto dagli input iniziali di un programma di breve durata (< 1 ora) quindi è stato più pratico rieseguire un programma con gli stessi input da una build di debug o in un debugger per ottenere maggiori informazioni.)

Al momento, non sono sicuro che esista un grande vantaggio o svantaggio nel catturare le eccezioni al solo scopo di impedire che le eccezioni vadano via main().

Il piccolo vantaggio che mi viene in mente per consentire il superamento delle eccezioni main()è che provoca la std::exception::what()stampa del risultato sul terminale (almeno con i programmi compilati da gcc su Linux). D'altra parte, questo è banale da ottenere invece catturando tutte le eccezioni derivate da std::exceptione stampando il risultato std::exception::what()e se è desiderabile stampare un messaggio da un'eccezione che non deriva da std::exceptionallora deve essere catturato prima di partire main()per stampare il messaggio.

Lo svantaggio modesto che mi viene in mente per consentire alle eccezioni di superare il passato main()è che possono essere generati dump core indesiderati. Per un processo che utilizza una grande quantità di memoria, ciò può essere piuttosto fastidioso e il controllo del comportamento di dumping principale da un programma richiede chiamate di funzione specifiche del sistema operativo. D'altra parte, se si desidera un dump e un'uscita core, questo potrebbe invece essere raggiunto in qualsiasi momento chiamando std::abort()e un'uscita senza dump core può essere raggiunta in qualsiasi momento chiamando std::exit().

Aneddoticamente, non credo di aver mai visto il what(): ...messaggio predefinito stampato da un programma ampiamente distribuito dopo un crash.

Quali sono, se ve ne sono, gli argomenti forti a favore o contro il fatto che le eccezioni C ++ saltino fuori main()?

Modifica: ci sono molte domande generali sulla gestione delle eccezioni su questo sito. La mia domanda riguarda in particolare le eccezioni C ++ che non possono essere gestite e sono riuscite fino in fondo main()- forse un messaggio di errore può essere stampato ma è un errore di arresto della visualizzazione immediata.




@gnat La mia domanda è un po 'più specifica. Riguarda le eccezioni C ++ che non possono essere gestite piuttosto che la gestione delle eccezioni in qualsiasi linguaggio di programmazione in nessuna circostanza.
Prassolitico il

Per i programmi multi-thread, le cose possono essere più complesse o più esotiche (eccezioni
wrt

1
I ragazzi del software per Ariane 5 vorrebbero sicuramente aver colto tutte le eccezioni durante il primo lancio ...
Eric Towers,

Risposte:


25

Un problema nel lasciare che le eccezioni vadano oltre il principale è che il programma terminerà con una chiamata a std::terminatecui deve chiamare il comportamento predefinito std::abort. È solo l' implementazione definita se lo svolgimento dello stack viene eseguito prima di chiamare, in terminatemodo che il programma possa terminare senza chiamare un singolo distruttore! Se hai qualche risorsa che aveva davvero bisogno di essere ripristinata da una chiamata distruttiva, sei in un sottaceto ...


12
Se hai qualche risorsa che aveva davvero bisogno di essere ripristinata da una chiamata del distruttore, sei in difficoltà ... => Dato che (purtroppo) il C ++ è piuttosto soggetto a crash, e questo senza menzionare che il sistema operativo potrebbe decidere di uccidere il tuo programma in qualsiasi momento (ad esempio OOM Killer in Unix), il tuo programma (e il suo ambiente) devono essere personalizzati per supportare gli arresti anomali.
Matthieu M.

@MatthieuM. Stai dicendo che i distruttori non sono un buon posto per la pulizia delle risorse che dovrebbe verificarsi anche durante un incidente?
Prassolitico

6
@Praxeolitic: sto dicendo che non tutti gli arresti anomali srotolano lo stack, ad esempio std::abortnon lo fa e quindi le affermazioni fallite non lo fanno neanche. Vale anche la pena notare che sui moderni sistemi operativi, il sistema operativo stesso pulirà molte risorse (memoria, handle di file, ...) che sono legate all'ID del processo. Infine, ho anche accennato a "Difesa approfondita": non è affidabile aspettarsi che tutti gli altri processi siano a prova di bug e rilasceranno sempre le risorse acquisite (sessioni, finisci bene la scrittura di file, ...) che devi pianificare esso ...
Matthieu M.

3
@Praxeolitic No, il tuo software non deve corrompere i dati durante un'interruzione di corrente. E se riesci a gestirlo, puoi anche riuscire a non corrompere i dati dopo un'eccezione non gestita.
user253751

1
@Praxeolitic: In realtà, questo esiste. Ti consigliamo di ascoltare il WM_POWERBROADCASTmessaggio. Funziona solo se il tuo computer è alimentato a batteria (se stai usando un laptop o un UPS).
Brian,

28

Il motivo principale per cui non si fa scappare le eccezioni mainè perché altrimenti si perde ogni possibilità di controllare come il problema viene segnalato ai propri utenti.

Per un programma che non è destinato a essere utilizzato a lungo o ampiamente distribuito, può essere accettabile che vengano segnalati errori imprevisti in qualsiasi modo il sistema operativo decida di farlo (ad esempio, mostrando una finestra di dialogo di errore in-your-face su Windows ).

Per i programmi che vendi o che sono forniti al pubblico da un'organizzazione che ha una reputazione da difendere, è generalmente una buona idea segnalare in modo piacevole che hai riscontrato un problema imprevisto e provare a salvare la maggior parte dei dati dell'utente il più possibile. Non perdere mezza giornata di lavoro del tuo utente e non andare in crash inaspettatamente, ma chiudere semi-con grazia è generalmente molto meglio per la tua reputazione aziendale rispetto all'alternativa.


Sei sicuro che il comportamento predefinito di un'eccezione C ++ che lascia main() abbia molto a che fare con il sistema operativo? Il sistema operativo potrebbe non sapere nulla di C ++. La mia ipotesi era che è determinato dal codice che il compilatore inserisce da qualche parte nel programma.
Prassolitico il

5
@Praxeolitic: è più che il sistema operativo imposta convenzioni e il compilatore genera codice per soddisfare tali convenzioni quando un programma termina in modo imprevisto. La linea di fondo è che la terminazione imprevista del programma dovrebbe essere evitata ogniqualvolta ragionevolmente possibile.
Bart van Ingen Schenau,

Perdi il controllo solo nell'ambito di C ++ standard. Dovrebbe essere disponibile un modo specifico di implementazione per gestire tali "arresti anomali". Il C ++ di solito non funziona nel vuoto.
Martin Ba

Quella finestra di dialogo di errore in-your-face può essere configurata on / off nel registro per quello che vale - ma per farlo è necessario il controllo del registro - e la maggior parte dei programmi non sono in esecuzione in modalità kiosk (dopo aver preso il controllo il sistema operativo).
PerryC,

11

TL; DR : cosa dice la specifica?


Una deviazione tecnica ...

Quando viene generata un'eccezione e nessun gestore è pronto per questo:

  • viene definita l'implementazione indipendentemente dal fatto che lo stack sia svolto o meno
  • std::terminate viene chiamato, che per impostazione predefinita si interrompe
  • a seconda della configurazione dell'ambiente, l'interruzione potrebbe o meno lasciare un rapporto sugli arresti anomali

Quest'ultimo può essere utile per bug molto rari (perché riprodurli è un processo che fa perdere tempo).


Se cogliere tutte le eccezioni o meno è, in definitiva, una questione di specifica:

  • è specificato come segnalare gli errori dell'utente? (valore non valido, ...)
  • è specificato come segnalare errori funzionali? (directory di destinazione mancante, ...)
  • è specificato come segnalare errori tecnici? (affermazione che si accende, ...)

Per qualsiasi programma di produzione, questo dovrebbe essere specificato e dovresti seguire le specifiche (e forse sostenere che è stato modificato).

Per programmi rapidamente messi insieme per essere utilizzati solo da personale tecnico (tu, i tuoi compagni di squadra), va bene lo stesso. Ti consiglio di lasciarlo andare in crash e configurare l'ambiente per ottenere un rapporto o meno a seconda delle tue esigenze.


1
Questo. I fattori decisivi sono stati. Sono sostanzialmente al di fuori dell'ambito del C ++.
Martin Ba

4

Un'eccezione rilevata ti dà la possibilità di stampare un bel messaggio di errore o persino provare a recuperare l'errore (possibilmente semplicemente riavviando l'applicazione).

Tuttavia, in C ++ un'eccezione non contiene informazioni sullo stato del programma al momento del lancio. Se lo prendi, tutto questo stato viene dimenticato, mentre se lasci che il programma si blocchi, lo stato è di solito ancora lì e può essere letto dal dump del programma, rendendo più facile il debug.

Quindi è un compromesso.


4

Nel momento in cui sai che devi interrompere, vai avanti e chiama std::terminategià per ridurre qualsiasi ulteriore danno.

Se sai che puoi rilassarti in sicurezza, fallo invece. Ricorda che lo svolgersi in pila non è garantito quando un'eccezione non viene mai rilevata, quindi prendi e ripeti.

Se puoi segnalare / registrare in modo sicuro l'errore meglio di quanto il sistema lo farà da solo, vai avanti.
Ma assicurati davvero di non peggiorare inavvertitamente le cose mentre lo fai.

Spesso è troppo tardi per salvare i dati quando si rileva un errore irreversibile, anche se dipende dall'errore specifico.
Ad ogni modo, se il tuo programma è stato scritto per un rapido recupero, ucciderlo potrebbe essere il modo migliore per terminarlo, anche se è solo un normale arresto.


1

Schiantarsi con grazia è una buona cosa per la maggior parte del tempo, ma ci sono dei compromessi. A volte è una buona cosa andare in crash. Dovrei menzionare che sto pensando principalmente al debug in dimensioni molto grandi. Per un programma semplice - sebbene ciò possa essere ancora utile, non è per nulla utile quanto lo è con un programma molto complesso (o diversi programmi complessi che interagiscono).

Non vuoi andare in crash in pubblico (anche se questo è davvero inevitabile: i programmi si arrestano in modo anomalo e un programma non crashing verificabile matematicamente non è ciò di cui stiamo parlando qui). Pensa a Bill Gates BSODing nel mezzo di una demo - male, vero? Tuttavia, possiamo provare a capire perché ci siamo schiantati e non si schiantano di nuovo allo stesso modo.

Ho attivato una funzionalità di Segnalazione errori di Windows che crea dump di arresto anomalo locale su eccezioni non gestite. Funziona a meraviglia se hai i file dei simboli associati alla tua build (in crash). È interessante notare che, poiché ho impostato questo strumento, voglio un arresto anomalo di più , perché imparo da ogni arresto anomalo. Quindi posso correggere i bug e bloccarmi di meno.

Quindi per ora, voglio che i miei programmi arrivino fino in cima e vadano in crash. In futuro, potrei voler mangiare con grazia tutte le mie eccezioni, ma non se riesco a farle funzionare per me.

Puoi leggere ulteriori informazioni sui dump di arresto locale qui se sei interessato: Raccolta di dump in modalità utente


-6

Alla fine, se un'eccezione passa sopra main (), andrà in crash la tua applicazione, e nella mia mente un'app non dovrebbe mai andare in crash. Se è possibile arrestare in modo anomalo un'app in un unico posto, perché non ovunque? Perché preoccuparsi della gestione delle eccezioni? (Sarcasmo, non proprio suggerendo questo ...)

Potresti avere un tentativo / cattura globale che stampa un messaggio elegante che dice all'utente che si è verificato un errore irreversibile e che hanno bisogno di inviarlo via e-mail in IT su di esso o qualcosa di simile, ma l'arresto anomalo è completamente non professionale e inaccettabile. È banale da prevenire e puoi informare un utente su ciò che è appena accaduto e su come risolvere le cose in modo che non accada di nuovo.

Lo schianto è un'ora strettamente amatoriale.


8
Quindi, meglio far finta che tutto funzioni correttamente e sovrascrivere invece tutta la memoria permanente con incomprensioni?
Deduplicatore

1
@Deduplicator: No: puoi sempre prendere l'eccezione e gestirla.
Giorgio

5
Se sai come gestirlo, probabilmente lo gestirai molto prima di iniziare main. Se non sai come gestirlo, fingere di farlo è ovviamente un bug davvero orribile.
Deduplicatore

8
ma l'arresto anomalo è completamente non professionale e inaccettabile => passare il tempo a lavorare su funzionalità inutili non è professionale: l'arresto anomalo è importante solo se conta per l'utente finale, in caso contrario, quindi si arresta in modo anomalo (e ottiene un rapporto di arresto anomalo, con un dump della memoria) è più economico.
Matthieu M.

Matthieu M. se ti ci vuole davvero tanto sforzo per registrare un errore, salvare tutti i file aperti e dipingere un messaggio amichevole sullo schermo, non so cosa dire. Se ritieni che sia inutile fallire con grazia, probabilmente dovresti parlare con chiunque stia facendo supporto tecnico per il tuo codice.
Roger Hill,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.