Come viene implementato il runtime di gestione delle eccezioni C ++?


84

Sono incuriosito da come funziona il meccanismo di gestione delle eccezioni C ++. In particolare, dove viene archiviato l'oggetto eccezione e come si propaga attraverso diversi ambiti finché non viene rilevato? È archiviato in un'area globale?

Poiché questo potrebbe essere specifico del compilatore, qualcuno potrebbe spiegarlo nel contesto della suite del compilatore g ++?


4
Leggere questo articolo ti aiuterà
detto Ahmed il

Non lo so, ma immagino che la specifica C ++ abbia una definizione chiara. (Potrei sbagliarmi però)
Paul Nathan,

2
No, le specifiche non danno una definizione. Determina il comportamento, non l'implementazione. Paul, potresti voler specificare quale implementazione ti interessa.
Rob Kennedy,


Fare riferimento alla sezione 5.4 del Rapporto tecnico sulle prestazioni C ++
Yogesh Arora

Risposte:


49

Le implementazioni possono differire, ma ci sono alcune idee di base che derivano dai requisiti.

L'oggetto eccezione stesso è un oggetto creato in una funzione, distrutto in un suo chiamante. Quindi, in genere non è possibile creare l'oggetto sullo stack. D'altra parte, molti oggetti eccezione non sono molto grandi. Ergo, si può creare, ad esempio, un buffer da 32 byte e un overflow da accumulare se è effettivamente necessario un oggetto eccezione più grande.

Per quanto riguarda l'effettivo trasferimento del controllo, esistono due strategie. Uno è registrare abbastanza informazioni nello stack stesso per srotolare lo stack. Questo è fondamentalmente un elenco di distruttori da eseguire e gestori di eccezioni che potrebbero catturare l'eccezione. Quando si verifica un'eccezione, eseguire nuovamente lo stack eseguendo quei distruttori fino a trovare un catch corrispondente.

La seconda strategia sposta queste informazioni in tabelle fuori dallo stack. Ora, quando si verifica un'eccezione, lo stack di chiamate viene utilizzato per scoprire quali ambiti vengono inseriti ma non chiusi. Questi vengono quindi cercati nelle tabelle statiche per determinare dove verrà gestita l'eccezione generata e quali distruttori verranno eseguiti nel mezzo. Ciò significa che c'è meno overhead di eccezioni sullo stack; gli indirizzi di ritorno sono comunque necessari. Le tabelle sono dati extra, ma il compilatore può inserirle in un segmento caricato a richiesta del programma.


4
AFAIR g ++ utilizza il secondo approccio della tabella degli indirizzi, presumibilmente per ragioni di compatibilità con C. Il compilatore C ++ di Microsoft utilizza un approccio combinato, poiché le sue eccezioni C ++ sono costruite sopra SEH (gestione delle eccezioni strutturate). In ogni funzione C ++, MSC ++ crea e registra un record di gestione delle eccezioni SEH, che punta a una tabella con intervalli di indirizzi per blocchi try-catch e distruttori in questa particolare funzione. lancia ai pacchetti un'eccezione C ++ come eccezione SEH e chiama RaiseException (), quindi SEH restituisce il controllo alla routine del gestore specifica di C ++.
Anton Tykhyy

1
@Anton: sì, utilizza l'approccio della tabella degli indirizzi. Vedi la mia risposta a un'altra domanda su stackoverflow.com/questions/307610/… per i dettagli.
CesarB

Grazie per la risposta. Puoi vedere come i puristi del C potrebbero essere terrorizzati dal C ++ e dalle sue eccezioni. L'idea che un semplice try / catch possa creare involontariamente un numero di oggetti stack in fase di esecuzione o gonfiare il programma con tabelle extra è il motivo per cui i sistemi embedded spesso li evitano.
velivolo

@speedplane: No, è più dovuto a una mancanza di comprensione. La gestione degli errori non è mai gratuita. C ti costringe a scriverlo da solo. E sappiamo tutti quanti programmi C mancano di un free()o di un fclose()percorso di codice usato raramente.
MSalters

@MSalters Non sono in disaccordo, è quasi interamente una mancanza di comprensione. Gli ingegneri spesso non capiscono come funzionano le eccezioni e come le eccezioni influenzeranno il loro codice, il che quindi, giustamente, porta a esitazione quando si usano le eccezioni. Se l'implementazione della gestione delle eccezioni fosse comunicata in modo più chiaro (e non sembrasse per magia), molti esiterebbero meno a usarli.
speedplane

20

Questo è definito in 15.1 Lanciare un'eccezione allo standard.

Il lancio crea un oggetto temporaneo.
Il modo in cui viene allocata la memoria per questo oggetto temporaneo non è specificato.

Dopo la creazione dell'oggetto temporaneo, il controllo viene passato al gestore più vicino nello stack di chiamate. svolgendo la pila tra lancio e punto di presa. Quando lo stack viene svolto, tutte le variabili dello stack vengono distrutte in ordine inverso rispetto alla creazione.

A meno che l'eccezione non venga rilanciata, il temporaneo viene eliminato alla fine del gestore in cui è stato rilevato.

Nota: se prendi per riferimento il riferimento farà riferimento al temporaneo, se prendi per valore l'oggetto temporaneo viene copiato nel valore (e quindi richiede un costruttore di copia).

Consigli di S.Meyers (Cattura per riferimento const).

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}

3
Qualcos'altro che non è specificato è come il programma svolge lo stack e come il programma sa dove si trova il "gestore più vicino". Sono abbastanza sicuro che Borland detenga un brevetto su un metodo per implementarlo.
Rob Kennedy,

Finché gli oggetti vengono distrutti in ordine inverso rispetto alla creazione, i dettagli di implementazione non sono importanti a meno che tu non sia un ingegnere compilatore.
Martin York

1
Votato: a) "Scott Meyers", non "S. Myers"; b) citazione falsa: "Effective C ++": "Item 13: Catch exceptions by reference. ". Ciò consentirà di modificare / aggiungere informazioni all'oggetto eccezione.
Sebastian Mach

3
@phresnel: non dimenticare l'elemento 21: "Usa const ogni volta che è possibile". Non ci sono buoni motivi per modificare un'eccezione. Dovresti essere a) "aggiustare e scartare", b) rilanciare o c) generare una nuova eccezione.
Martin York

1
@phresnel: Sì, hai le tue ragioni (non sono d'accordo con la tua logica), io ho le mie e anche se non pretenderò di aver parlato con loro di questo argomento specifico o di conoscere effettivamente le loro menti (Meyers, Alexandrescu e Sutter) credo la mia interpretazione è valida. Ma se ti trovi nell'area di Seattle, puoi parlare con tutti e tre poiché sono partecipanti regolari al North West C ++ User Group (Meyers meno spesso degli altri).
Martin York

13

Potresti dare un'occhiata qui per una spiegazione dettagliata.

Può anche essere utile dare un'occhiata a un trucco usato in C normale per implementare un tipo di base di gestione delle eccezioni. Ciò implica l'utilizzo di setjmp () e longjmp () nel modo seguente: il primo salva lo stack per contrassegnare il gestore delle eccezioni (come "catch"), mentre il secondo viene utilizzato per "lanciare" un valore. Il valore "generato" è visto come se fosse stato restituito da una funzione chiamata. Il "blocco try" termina quando setjmp () viene chiamato di nuovo o quando la funzione ritorna.


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.