Quanto ritieni importante la sicurezza delle eccezioni nel tuo codice C ++?


19

Ogni volta che penso di rendere il mio codice fortemente sicuro, giuro di non farlo perché richiederebbe così tanto tempo. Considera questo frammento relativamente semplice:

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

Per implementare una garanzia di eccezione di base, ho potuto utilizzare un puntatore con ambito sulle newchiamate. Ciò impedirebbe perdite di memoria se una delle chiamate generasse un'eccezione.

Tuttavia, diciamo che voglio implementare una forte garanzia di eccezione. Almeno, avrei bisogno di implementare un puntatore condiviso per i miei contenitori (non sto usando Boost), un nothrow Entity::Swapper aggiungere atomicamente i componenti e una sorta di linguaggio per aggiungere atomicamente sia a Vectorche a Map. Questi non solo richiederebbero molto tempo per essere implementati, ma sarebbero costosi poiché comportano una copia molto maggiore rispetto all'eccezionale soluzione non sicura.

In definitiva, mi sembra che il tempo trascorso a fare tutto ciò non sia giustificato solo perché una semplice CreateEntityfunzione è fortemente sicura dalle eccezioni. Probabilmente voglio solo che il gioco mostri un errore e chiuda comunque a quel punto.

Quanto lontano lo porti nei tuoi progetti di gioco? È generalmente accettabile scrivere un codice non sicuro di eccezione per un programma che può bloccarsi in caso di eccezione?


21
Quando ho lavorato a progetti C ++ in passato, fondamentalmente abbiamo fatto finta che non esistessero eccezioni.
Tetrad

2
Personalmente, adoro le eccezioni e spesso mi diverto a capire come far sì che il mio codice offra forti garanzie. Se hai intenzione di andare in crash quando ricevi un'eccezione, però ... forse non ti preoccupare.

7
Personalmente utilizzo le eccezioni per gestire ... hm .. "eccezioni" non errori. Ad esempio, se so che una funzione potrebbe bloccarsi, la lascio andare in crash, ma se so che una funzione potrebbe non leggere correttamente un archivio, ma c'è un altro modo, la circondo con un tentativo, cattura. e se è un'eccezione "incapace di leggere", la gestisco nell'altro modo senza la lettura dell'archivio.
Gustavo Maciel,

Ciò che Gtoknu ha detto, è necessario essere consapevoli delle eccezioni che qualsiasi libreria esterna può generare.
Peter Ølsted,

Risposte:


26

Se hai intenzione di usare le eccezioni (il che non vuol dire che dovresti, ci sono pro e contro in quell'approccio che sono al di fuori dell'ambito specifico di questa domanda), dovresti scrivere correttamente il codice sicuro dell'eccezione.

Il codice che non utilizza le eccezioni e che non è esplicitamente sicuro per le eccezioni è migliore del codice che le utilizza ma ne garantisce la sicurezza delle eccezioni. Se hai l'ultimo caso, hai un'intera classe di bug e guasti che possono verificarsi quando si verifica un'eccezione da cui probabilmente non puoi recuperare, rendendo uno dei vantaggi delle eccezioni completamente nullo. Anche se è una classe di fallimento relativamente rara, è ancora possibile.

Questo non vuol dire che se si usano le eccezioni, tutto il codice deve fornire la garanzia di eccezione forte - solo che ogni parte di codice dovrebbe fornire una garanzia di qualche tipo (nessuna, di base, forte) in modo che nel consumo di quel codice sai quali sono le garanzie che il consumatore può fornire. Generalmente più basso è il componente in questione, più forte dovrebbe essere la garanzia.

Se non fornirai mai una forte garanzia o non abbraccerai mai veramente i paradigmi di gestione delle eccezioni e tutto il lavoro extra e gli svantaggi che ciò implica, non otterrai davvero tutti i vantaggi che implica e potresti avere un modo più semplice tempo semplicemente rinunciando a eccezioni del tutto.


12
Questo da solo vale un +1: "Il codice che non usa eccezioni e che non è esplicitamente sicuro dalle eccezioni è meglio del codice che le usa ma ne sta a metà la sicurezza delle eccezioni."
Massimo

14

La mia regola generale: se è necessario utilizzare le eccezioni, utilizzare le eccezioni. Se non è necessario utilizzare le eccezioni, non utilizzare le eccezioni. Se non sai se è necessario o meno utilizzare le eccezioni, non è necessario utilizzare le eccezioni.

Eccezioni ci sono per essere la tua ultima linea di difesa assoluta in applicazioni mission-on sempre attive. Se stai scrivendo un software di controllo del traffico aereo che non può assolutamente fallire, usa le eccezioni. Se stai scrivendo un codice di controllo per una centrale nucleare, usa le eccezioni.

Quando si sviluppa un gioco, d'altra parte, è molto meglio seguire il mantra: schiantarsi presto, schiantarsi rumorosamente. Se c'è un problema, vuoi davvero saperlo al più presto; non vorrai mai che gli errori vengano "gestiti" in modo invisibile, perché questo spesso nasconderà la causa effettiva di comportamenti scorretti successivi e renderà il debugging molto più difficile di quanto debba essere.


5

Il problema con le eccezioni è che devi affrontarle comunque. Se si riceve un'eccezione a causa della memoria insufficiente, è possibile che non si riesca comunque a gestire tale errore. Potresti anche scaricare l'intero gioco in 1 gestore di eccezioni giganti che fa apparire una finestra di errore.

Per lo sviluppo del gioco preferisco provare a trovare modi per far sì che il mio codice continui gentilmente con errori quando possibile.

Ad esempio, codificherò una mesh ERRORE speciale nel mio gioco. È un cerchio rosso rotante con una X bianca e un bordo bianco. A seconda del gioco, uno invisibile potrebbe essere migliore. Ha un'istanza singleton inizializzata all'avvio. Dal momento che è inserito nel codice anziché utilizzare qualsiasi file esterno, non dovrebbe essere possibile fallire.

Nel caso in cui una normale chiamata loadMesh non riesca invece di restituire un errore e un arresto anomalo sul desktop, registrerà silenziosamente l'errore nella console / nel file di log e restituirà la mesh di errore codificata. Ho scoperto che Skyrim in realtà fa la stessa cosa quando una mod ha rovinato tutto. C'è una buona possibilità che il giocatore non vedrà nemmeno la maglia dell'errore (a meno che non ci siano alcuni problemi importanti e molti di loro sono simili).

C'è anche uno shader di fallback. Uno che esegue le operazioni di base della matrice del vertice ma se non esiste una matrice uniforme per qualche motivo, dovrebbe comunque eseguire la vista ortogonale. Non ha ombreggiatura / illuminazione / materiali adeguati (anche se ha un flag color_overide, quindi posso usarlo con la mia maglia di errore).

L'unico problema possibile è se la versione della mesh dell'errore potrebbe in qualche modo interrompere definitivamente il gioco. Ad esempio, assicurati che non applichi la fisica poiché se il giocatore carica un'area, allora la maglia dell'errore che la colpisce potrebbe cadere a terra, quindi se questa volta ricaricano il gioco con la maglia corretta funzionante, se è più grande potrebbe essere incastonato nel terreno. Non dovrebbe essere troppo difficile dal momento che probabilmente memorizzerai le tue maglie fisiche con quelle grafiche. Lo scripting potrebbe anche essere un problema. Con alcune cose dovrai solo morire duro. Non sono sicuro di quale uso sarebbe un "livello" di fallback (anche se potresti creare una piccola stanza con un cartello che dice che c'è stato un errore, assicurati solo che il salvataggio sia disabilitato poiché non vuoi che il giocatore vi rimanga bloccato).

Nel caso di "nuovo", per lo sviluppo di giochi potrebbe essere meglio usare una sorta di fabbrica. Può darti vari altri vantaggi come la preallocazione di oggetti prima che tu ne abbia realmente bisogno e / o renderli alla rinfusa. Inoltre semplifica la creazione di elementi come un indice globale di oggetti che è possibile utilizzare per la gestione della memoria, trovando le cose tramite un ID (anche se è possibile farlo anche nei costruttori). E ovviamente puoi anche gestire gli errori di allocazione.


2

La cosa importante della mancanza di controlli di runtime e crash è il potenziale per un hacker di utilizzare un crash per assumere il controllo del processo e forse successivamente della macchina.

Ora, un hacker non può sfruttare un vero incidente, non appena si verifica un incidente, il processo è inutile e innocuo. Se leggi semplicemente da un puntatore nullo, si tratta di un arresto anomalo, fai un errore e il processo muore all'istante.

Il pericolo si presenta quando si esegue un comando che non è tecnicamente illegale, ma non era quello che si intendeva fare, quindi un hacker potrebbe essere in grado di dirigere quell'azione utilizzando dati accuratamente predisposti che altrimenti dovrebbero essere sicuri.

Un'eccezione non rilevata non è in realtà un vero arresto, è solo un modo per terminare il programma. Le eccezioni si verificano solo perché qualcuno ha scritto una libreria che genera specificamente un'eccezione in determinate circostanze. In genere per evitare di entrare in una situazione inaspettata, che potrebbe essere un'apertura per un hacker.

In realtà, la mossa pericolosa è catturare le eccezioni piuttosto che lasciarle scivolare, ciò non significa che non dovresti mai farlo, ma assicurati di catturare solo quelli che sai di poter gestire e lasciare che il resto scivoli. Altrimenti rischi di annullare le misure di sicurezza messe con cura nella tua biblioteca.

Concentrarsi sugli errori che non generano necessariamente eccezioni, come la scrittura oltre la fine di un array. Il tuo programma non potrebbe per caso farlo?


2

È necessario esaminare le eccezioni e le condizioni che possono attivarle. Un sacco di tempo, una grande percentuale di ciò che le persone usano per le eccezioni può essere evitata da controlli adeguati durante il ciclo di sviluppo / costruzione di debug / test. Usa assert, passa riferimenti, inizializza sempre variabili locali su dichiarazione, memset-zero tutte le allocazioni, NULL dopo libero, ecc., E un sacco di casi teorici d'eccezione scompaiono.

Considera: se qualcosa fallisce e può essere un candidato per lanciare un'eccezione, qual è l'impatto? Quanto conta? Se fallisci un'allocazione di memoria in un gioco (per fare il tuo esempio originale), in genere hai un problema più grande tra le mani di come gestisci il caso di fallimento e gestire il caso di fallimento per mezzo di un'eccezione non causerà quel problema più grande va via. Peggio ancora, in realtà potrebbe nascondere il fatto che il problema più grande è persino lì. Lo vuoi? So di no. So che se fallisco un'allocazione di memoria voglio andare in crash e bruciare in modo orribile e farlo il più vicino possibile al punto di errore, in modo da poter entrare nel debugger, capire cosa sta succedendo e risolverlo correttamente.


1

Non sono sicuro se citare qualcun altro sia appropriato su SE, ma non credo che nessuna delle altre risposte abbia davvero toccato questo.

Citando Jason Gregory dal suo libro Game Engine Architecture . (GRANDE LIBRO!)

"La gestione strutturata delle eccezioni (SEH) aggiunge un sacco di sovraccarico al programma. Ogni frame dello stack deve essere aumentato per contenere informazioni aggiuntive richieste dal processo di svolgimento dello stack. Inoltre, lo svolgimento dello stack è di solito molto lento - nell'ordine da due a tre volte più costoso del semplice ritorno dalla funzione. Inoltre, se anche una sola funzione nel tuo programma (o libreria) utilizza SEH, l'intero programma deve utilizzare SEH. Il compilatore non può sapere quali funzioni potrebbero essere sopra di te nello stack di chiamate quando si lancia un'eccezione ".

Detto questo, il mio motore che sto costruendo non usa eccezioni. Ciò è dovuto ai costi delle prestazioni potenziali precedentemente menzionati e al fatto che per tutti i miei scopi i codici di ritorno dell'errore funzionano bene. L'unica volta in cui ho davvero bisogno di cercare guasti è l'inizializzazione e il caricamento delle risorse e in quei casi restituire un valore booleano che indica il successo funziona bene.


2
La gestione strutturata delle eccezioni è un tipo specifico di gestione delle eccezioni disponibile su Windows, che non è generalmente uguale alle eccezioni C ++.
Kylotan,

Doh, non posso credere di non saperlo. Grazie per la correzione!
KlashnikovKid

0

Rendo sempre sicura la mia eccezione di codice, semplicemente perché rende più facile sapere dove sorgono problemi, poiché posso registrare qualcosa ogni volta che viene generata o rilevata un'eccezione.

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.