Linee guida generali per evitare perdite di memoria in C ++ [chiuso]


130

Quali sono alcuni suggerimenti generali per assicurarsi di non perdere la memoria nei programmi C ++? Come faccio a capire chi dovrebbe liberare memoria che è stata allocata dinamicamente?


26
Mi sembra abbastanza costruttivo.
Shoerob,

11
Questo è costruttivo. E le risposte sono supportate da fatti, competenze, riferimenti, ecc. E vedi il numero di voti / risposte .. !!
Samitha Chathuranga il

Risposte:



200

Approvo a fondo tutti i consigli su RAII e gli smart pointer, ma vorrei anche aggiungere un suggerimento di livello leggermente superiore: la memoria più semplice da gestire è la memoria che non hai mai allocato. A differenza di linguaggi come C # e Java, dove praticamente tutto è un riferimento, in C ++ dovresti mettere gli oggetti nello stack ogni volta che puoi. Come ho visto diverse persone (incluso il dottor Stroustrup) sottolineare, il motivo principale per cui la raccolta dei rifiuti non è mai stata popolare in C ++ è che il C ++ ben scritto non produce molta spazzatura in primo luogo.

Non scrivere

Object* x = new Object;

o anche

shared_ptr<Object> x(new Object);

quando puoi semplicemente scrivere

Object x;

34
Vorrei poterlo dare un +10. Questo è il problema più grande che vedo oggi con la maggior parte dei programmatori C ++, e presumo sia perché hanno imparato Java prima del C ++.
Kristopher Johnson,

Punto molto interessante: mi ero chiesto perché ho problemi di gestione della memoria in C ++ molto meno spesso rispetto ad altre lingue, ma ora capisco perché: in realtà consente alle cose di andare in pila come in vanilla C.
ArtOfWarfare

Quindi cosa fai se scrivi Object x; e poi vuoi buttare via x? dire che x è stato creato nel metodo principale.
Yamcha,

3
@ user1316459 C ++ consente anche di creare ambiti al volo. Tutto quello che devi fare è avvolgere la vita di x tra parentesi graffe in questo modo: {Object x; x.DoSomething; }. Dopo l'ultimo '}', il distruttore di x verrà chiamato liberando tutte le risorse in esso contenute. Se x, di per sé, è la memoria da allocare sull'heap, suggerisco di racchiuderlo in un unique_ptr in modo che venga ripulito facilmente e in modo appropriato.
David Peterson,

1
Robert: si. Ross non ha detto "Non scrivere mai [codice contenente nuovo]", ha detto "Non scrivere [quello] quando puoi semplicemente [metterlo in pila]". Gli oggetti di grandi dimensioni sull'heap continueranno a essere la chiamata giusta nella maggior parte delle situazioni, in particolare per il codice ad alte prestazioni.
codetaku,

104

Usa RAII

  • Dimentica Garbage Collection (usa invece RAII). Si noti che anche Garbage Collector può perdere anche (se si dimentica di "null" alcuni riferimenti in Java / C #) e che Garbage Collector non ti aiuterà a smaltire le risorse (se si dispone di un oggetto che ha acquisito un handle per un file, il file non verrà liberato automaticamente quando l'oggetto uscirà dall'ambito se non lo si fa manualmente in Java, o si utilizza il modello "dispose" in C #).
  • Dimentica la regola "un ritorno per funzione" . Questo è un buon consiglio C per evitare perdite, ma è obsoleto in C ++ a causa del suo uso di eccezioni (usare RAII invece).
  • E mentre il "modello a sandwich" è un buon consiglio in C, è obsoleto in C ++ a causa del suo uso di eccezioni (usa RAII invece).

Questo post sembra essere ripetitivo, ma in C ++, il modello più semplice da sapere è RAII .

Impara a usare i puntatori intelligenti, sia da boost, TR1 o anche da auto_ptr (ma spesso abbastanza efficiente) (ma devi conoscerne i limiti).

RAII è la base della sicurezza delle eccezioni e dello smaltimento delle risorse in C ++, e nessun altro modello (sandwich, ecc.) Ti darà entrambi (e il più delle volte, non ti darà nessuno).

Vedi sotto un confronto di codice RAII e non RAII:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

A proposito di RAII

Per riassumere (dopo il commento di Ogre Psalm33 ), RAII si basa su tre concetti:

  • Una volta che l'oggetto è stato costruito, funziona e basta! Acquisisci risorse nel costruttore.
  • La distruzione dell'oggetto è sufficiente! Fai risorse gratuite nel distruttore.
  • Si tratta di ambiti! Gli oggetti con ambito (vedi l'esempio doRAIIStatic sopra) saranno costruiti alla loro dichiarazione e saranno distrutti nel momento in cui l'esecuzione esce dall'ambito, indipendentemente da come l'uscita (ritorno, interruzione, eccezione, ecc.).

Ciò significa che nel corretto codice C ++, la maggior parte degli oggetti non verrà costruita con newe verrà invece dichiarata nello stack. E per quelli costruiti usando new, tutto sarà in qualche modo mirato (ad esempio collegato a un puntatore intelligente).

Come sviluppatore, questo è davvero molto potente in quanto non dovrai preoccuparti della gestione manuale delle risorse (come fatto in C, o per alcuni oggetti in Java che fanno un uso intensivo di try/ finallyper quel caso) ...

Modifica (2012-02-12)

"Gli oggetti con ambito ... verranno distrutti ... indipendentemente dall'uscita" non è del tutto vero. ci sono modi per imbrogliare RAII. qualsiasi sapore di terminate () ignorerà la pulizia. exit (EXIT_SUCCESS) è un ossimoro in questo senso.

- Wilhelmtell

Wilhelmtell ha ragione: ci sono modi eccezionali per imbrogliare RAII, il che porta a un brusco arresto del processo.

Questi sono modi eccezionali perché il codice C ++ non è disseminato di terminare, uscire, ecc., O nel caso delle eccezioni, vogliamo un'eccezione non gestita per arrestare il processo e scaricare la memoria come core, e non dopo la pulizia.

Ma dobbiamo ancora conoscere questi casi perché, anche se raramente accadono, possono ancora accadere.

(chi chiama terminateo exitnel codice C ++ casuale? ... Ricordo di aver dovuto affrontare quel problema quando giocavo con GLUT : questa libreria è molto orientata al C, arrivando al punto di progettarla attivamente per rendere le cose difficili per gli sviluppatori C ++ come non preoccuparsene sullo stack di dati allocati o sull'avere decisioni "interessanti" sul non tornare mai dal loro ciclo principale ... non commenterò al riguardo) .


La classe T non deve usare RAII per essere sicuro che doRAIIStatic () non perde la memoria? Ad esempio T p (); p.doSandwich (); Ma non ne so davvero molto.
Daniel O

@Ogre Psalm33: Grazie per il commento. Certo, hai ragione. Ho aggiunto entrambi i collegamenti alla pagina Wikipedia di RAII e un piccolo riassunto di ciò che è RAII.
paercebal,

1
@Shiftbit: tre modi, in ordine di preferenza: _ _ _ 1. Inserisci l'oggetto reale all'interno del contenitore STL. _ _ _ 2. Inserisci i puntatori intelligenti (shared_ptr) di oggetti all'interno del contenitore STL. _ _ _ 3. Inserisci i puntatori non elaborati all'interno del contenitore STL, ma avvolgi il contenitore per controllare qualsiasi accesso ai dati. Il wrapper si assicurerà che il distruttore libererà gli oggetti allocati e gli accessori del wrapper si accerteranno che nulla venga rotto durante l'accesso / la modifica del contenitore.
paercebal,

1
@Robert: in C ++ 03, useresti doRAIIDynamic in una funzione che deve dare la proprietà a una funzione figlio o genitore (o ambito globale). O quando si riceve un'interfaccia per un oggetto polimorfo attraverso una fabbrica (restituendo un puntatore intelligente, se è scritto correttamente). In C ++ 11, questo è meno vero perché puoi rendere mobile il tuo oggetto, quindi dare la proprietà di un oggetto dichiarato in pila è più facile ...
paercebal

2
@Robert: ... Nota che dichiarare un oggetto nello stack non significa che l'oggetto non usi l'heap internamente (nota la doppia negazione ... :-) ...). Ad esempio, std :: string implementato con Small String Optimization avrà un buffer "nello stack della classe" per stringhe piccole (~ 15 caratteri) e utilizzerà un puntatore a una memoria nell'heap per stringhe più grandi ... Ma dall'esterno, std :: string è ancora un tipo di valore che dichiari (di solito) nello stack e usi come useresti un numero intero (al contrario di: come useresti un'interfaccia per una classe polimorfica).
paercebal,

25

Ti consigliamo di guardare i puntatori intelligenti, come i puntatori intelligenti di boost .

Invece di

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost :: shared_ptr verrà automaticamente eliminato quando il conteggio dei riferimenti è zero:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

Nota la mia ultima nota, "quando il conteggio dei riferimenti è zero, che è la parte più interessante. Quindi, se hai più utenti del tuo oggetto, non dovrai tenere traccia del fatto che l'oggetto sia ancora in uso. Una volta che nessuno si riferisce al tuo puntatore condiviso, viene distrutto.

Questa non è una panacea, tuttavia. Sebbene sia possibile accedere al puntatore di base, non si vorrebbe passarlo a un'API di terze parti a meno che non si fosse sicuri di ciò che stava facendo. Molte volte, le tue "pubblicazioni" su qualche altra discussione per il lavoro da fare DOPO che l'ambito di creazione è finito. Questo è comune con PostThreadMessage in Win32:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

Come sempre, usa il tuo cappello pensante con qualsiasi strumento ...



11

La maggior parte delle perdite di memoria sono il risultato di non essere chiari sulla proprietà e sulla durata degli oggetti.

La prima cosa da fare è allocare in pila ogni volta che puoi. Questo riguarda la maggior parte dei casi in cui è necessario allocare un singolo oggetto per qualche scopo.

Se hai bisogno di "nuovo" un oggetto, la maggior parte delle volte avrà un unico proprietario ovvio per il resto della sua vita. Per questa situazione, tendo ad usare un sacco di modelli di raccolte che sono progettati per "possedere" oggetti memorizzati in essi tramite puntatore. Sono implementati con i contenitori di vettore e mappa STL ma presentano alcune differenze:

  • Queste raccolte non possono essere copiate o assegnate a. (una volta che contengono oggetti.)
  • I puntatori agli oggetti vengono inseriti in essi.
  • Quando la raccolta viene eliminata, il distruttore viene prima chiamato su tutti gli oggetti nella raccolta. (Ho un'altra versione in cui si afferma se distrutta e non vuota.)
  • Poiché memorizzano i puntatori, è anche possibile archiviare oggetti ereditati in questi contenitori.

Il mio problema con STL è che è così focalizzato sugli oggetti Value mentre nella maggior parte delle applicazioni gli oggetti sono entità uniche che non hanno una semantica di copia significativa richiesta per l'uso in quei contenitori.


10

Bah, voi ragazzi e i vostri nuovi raccoglitori di immondizia ...

Regole molto rigide sulla "proprietà": quale oggetto o parte del software ha il diritto di eliminare l'oggetto. Cancella commenti e saggi nomi di variabili per renderlo ovvio se un puntatore "possiede" o è "guarda, non toccare". Per decidere chi possiede cosa, segui il più possibile il modello "sandwich" all'interno di ogni subroutine o metodo.

create a thing
use that thing
destroy that thing

A volte è necessario creare e distruggere in luoghi molto diversi; penso intensamente per evitarlo.

In qualsiasi programma che richieda strutture dati complesse, creo un rigoroso albero di oggetti ben definito contenente altri oggetti, usando i puntatori "proprietario". Questo albero modella la gerarchia di base dei concetti del dominio dell'applicazione. Esempio una scena 3D possiede oggetti, luci, trame. Alla fine del rendering quando il programma si chiude, c'è un modo chiaro per distruggere tutto.

Molti altri puntatori sono definiti come necessari ogni volta che un'entità ha bisogno di accedervi, per scansionare arays o altro; questi sono i "solo guardando". Per l'esempio della scena 3D - un oggetto usa una trama ma non possiede; altri oggetti possono usare la stessa trama. La distruzione di un oggetto non invoca la distruzione di alcuna trama.

Sì, richiede tempo, ma è quello che faccio. Raramente ho perdite di memoria o altri problemi. Ma poi lavoro nell'arena limitata del software scientifico, di acquisizione dati e grafica ad alte prestazioni. Spesso non gestisco transazioni come nel settore bancario ed e-commerce, GUI guidate da eventi o caos asincrono ad alta rete. Forse i nuovi modi hanno un vantaggio lì!


Sono assolutamente d'accordo. Lavorando in un ambiente incorporato, potresti anche non avere il lusso di librerie di terze parti.
simon,

6
Non sono d'accordo. nella parte di "usa quella cosa", se viene generato un ritorno o un'eccezione, allora perderai la deallocazione. Per quanto riguarda le prestazioni, lo std :: auto_ptr non ti costerebbe nulla. Non che non scrivo mai codice nello stesso modo in cui lo fai. È solo che c'è una differenza tra il 100% e il 99% di codice sicuro. :-)
paercebal,

8

Ottima domanda!

se stai usando c ++ e stai sviluppando un'applicazione boud in tempo reale su CPU e memoria (come i giochi) devi scrivere il tuo Memory Manager.

Penso che il meglio che puoi fare sia unire alcune opere interessanti di vari autori, posso darti un suggerimento:

  • L'allocatore a dimensione fissa è ampiamente discusso, ovunque nella rete

  • Small Object Allocation è stata introdotta da Alexandrescu nel 2001 nel suo libro perfetto "Modern c ++ design"

  • Un grande progresso (con il codice sorgente distribuito) può essere trovato in un fantastico articolo in Game Programming Gem 7 (2008) chiamato "High Performance Heap allocator" scritto da Dimitar Lazarov

  • Un grande elenco di risorse è disponibile in questo articolo

Non iniziare a scrivere da solo un allocatore inutile ... No prima DOCUMENTO.


5

Una tecnica che è diventata popolare con la gestione della memoria in C ++ è RAII . Fondamentalmente usi costruttori / distruttori per gestire l'allocazione delle risorse. Naturalmente ci sono altri dettagli odiosi in C ++ a causa della sicurezza delle eccezioni, ma l'idea di base è piuttosto semplice.

Il problema generalmente si riduce a quello di proprietà. Consiglio vivamente di leggere la serie C ++ Effective di Scott Meyers e Modern C ++ Design di Andrei Alexandrescu.



4

Puntatori intelligenti per l'utente ovunque tu sia! Intere classi di perdite di memoria scompaiono.


4

Condividi e conosci le regole di proprietà della memoria nel tuo progetto. L'uso delle regole COM garantisce la migliore coerenza (i parametri [in] sono di proprietà del chiamante, il destinatario deve copiare; i parametri [out] sono di proprietà del chiamante, il destinatario deve effettuare una copia se si mantiene un riferimento; ecc.)


4

valgrind è un ottimo strumento per controllare anche le perdite di memoria dei programmi in fase di esecuzione.

È disponibile sulla maggior parte delle versioni di Linux (incluso Android) e su Darwin.

Se usi per scrivere unit test per i tuoi programmi, dovresti prendere l'abitudine di eseguire sistematicamente valgrind sui test. Potrà evitare molte perdite di memoria in una fase iniziale. Di solito è anche più facile individuarli in semplici test che in un software completo.

Naturalmente questo consiglio rimane valido per qualsiasi altro strumento di controllo della memoria.


3

Inoltre, non utilizzare la memoria allocata manualmente se esiste una classe di libreria std (ad es. Vettore). Assicurati di violare quella regola di avere un distruttore virtuale.


2

Se non puoi / non utilizzare un puntatore intelligente per qualcosa (anche se dovrebbe essere un'enorme bandiera rossa), digita il codice con:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

Questo è ovvio, ma assicurati di digitarlo prima di digitare qualsiasi codice nell'ambito


2

Una fonte frequente di questi bug è quando si dispone di un metodo che accetta un riferimento o un puntatore a un oggetto ma lascia la proprietà poco chiara. Convenzioni di stile e commenti possono renderlo meno probabile.

Lascia che il caso in cui la funzione diventi proprietaria dell'oggetto sia il caso speciale. In tutte le situazioni in cui ciò accade, assicurarsi di scrivere un commento accanto alla funzione nel file di intestazione indicando questo. Dovresti cercare di assicurarti che nella maggior parte dei casi anche il modulo o la classe che alloca un oggetto sia responsabile della deallocazione.

L'uso di const può aiutare molto in alcuni casi. Se una funzione non modifica un oggetto e non memorizza un riferimento a esso persistente dopo la sua restituzione, accetta un riferimento const. Dalla lettura del codice del chiamante sarà ovvio che la tua funzione non ha accettato la proprietà dell'oggetto. Avresti potuto fare in modo che la stessa funzione accettasse un puntatore non const e il chiamante poteva o meno supporre che la chiamata accettasse la proprietà, ma con un riferimento const non c'è dubbio.

Non utilizzare riferimenti non costanti negli elenchi di argomenti. Non è molto chiaro quando si legge il codice del chiamante che la chiamata potrebbe aver mantenuto un riferimento al parametro.

Non sono d'accordo con i commenti che raccomandano puntatori contati di riferimento. Questo di solito funziona bene, ma quando hai un bug e non funziona, specialmente se il tuo distruttore fa qualcosa di non banale, come in un programma multithread. Sicuramente prova a modificare il tuo design per non aver bisogno del conteggio dei riferimenti se non è troppo difficile.


2

Suggerimenti in ordine di importanza:

-Tip # 1 Ricorda sempre di dichiarare i tuoi distruttori "virtuali".

-Tip # 2 Usa RAII

-Tip # 3 Usa gli smartpointer boost

-Tipo n. 4 Non scrivere i tuoi smartpointer con errori, usa boost (su un progetto in questo momento non riesco a usare boost, e ho sofferto il debug dei miei puntatori intelligenti, sicuramente non prenderei lo stesso percorso di nuovo, ma poi di nuovo in questo momento non posso aggiungere spinta alle nostre dipendenze)

-Tip # 5 Se funziona in modo casual / non performante (come nei giochi con migliaia di oggetti), guarda il contenitore puntatore boost di Thorsten Ottosen

-Tip # 6 Trova un'intestazione di rilevamento delle perdite per la tua piattaforma preferita come l'intestazione "vld" di Visual Leak Detection


Forse mi manca un trucco, ma come possono essere "gioco" e "non critici per le prestazioni" nella stessa frase?
Adam Naylor,

I giochi sono ovviamente un esempio dello scenario critico. Potrebbe non essere stato chiaro lì
Robert Gould,

Suggerimento n. 1 dovrebbe essere applicato solo se la classe ha almeno un metodo virtuale. Non imporrei mai un inutile distruttore virtuale su una classe che non è destinata a servire come classe base in un albero di eredità polimorfico.
antred

1

Se puoi, usa boost shared_ptr e standard C ++ auto_ptr. Questi trasmettono la semantica della proprietà.

Quando si restituisce un auto_ptr, si sta dicendo al chiamante che si sta dando loro la proprietà della memoria.

Quando restituisci un shared_ptr, stai dicendo al chiamante che hai un riferimento ad esso e che prendono parte della proprietà, ma non è solo una loro responsabilità.

Questa semantica si applica anche ai parametri. Se il chiamante ti passa un auto_ptr, ti stanno dando la proprietà.


1

Altri hanno menzionato i modi per evitare perdite di memoria in primo luogo (come i puntatori intelligenti). Ma uno strumento di profilazione e analisi della memoria è spesso l'unico modo per rintracciare i problemi di memoria una volta che li hai.

Il memcheck di Valgrind è eccellente e gratuito.


1

Solo per MSVC, aggiungi quanto segue all'inizio di ogni file .cpp:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

Quindi, durante il debug con VS2003 o superiore, ti verrà comunicato di eventuali perdite quando il tuo programma esce (tiene traccia di nuovo / elimina). È di base, ma mi ha aiutato in passato.


1

valgrind (disponibile solo per piattaforme * nix) è un ottimo controller di memoria


1

Se hai intenzione di gestire la tua memoria manualmente, hai due casi:

  1. Ho creato l'oggetto (forse indirettamente, chiamando una funzione che alloca un nuovo oggetto), lo uso (o una funzione che chiamo lo usa), quindi lo libero.
  2. Qualcuno mi ha dato il riferimento, quindi non dovrei liberarlo.

Se devi infrangere una di queste regole, ti preghiamo di documentarlo.

Si tratta della proprietà del puntatore.


1
  • Cerca di evitare l'allocazione dinamica degli oggetti. Finché le classi hanno costruttori e distruttori appropriati, usa una variabile del tipo di classe, non un puntatore ad essa, ed eviti allocazione dinamica e deallocazione perché il compilatore lo farà per te.
    In realtà questo è anche il meccanismo usato da "puntatori intelligenti" e indicato come RAII da alcuni degli altri scrittori ;-).
  • Quando si passano oggetti ad altre funzioni, preferire i parametri di riferimento ai puntatori. Questo evita alcuni possibili errori.
  • Dichiarare i parametri const, ove possibile, in particolare i puntatori agli oggetti. In questo modo gli oggetti non possono essere liberati "accidentalmente" (tranne se si lancia via la const ;-))).
  • Ridurre al minimo il numero di posizioni nel programma in cui si esegue l'allocazione e la deallocazione della memoria. Per esempio. se assegnate o liberate più volte lo stesso tipo, scrivete una funzione (o un metodo di fabbrica ;-)).
    In questo modo è possibile creare facilmente output di debug (quali indirizzi sono allocati e deallocati, ...) facilmente, se necessario.
  • Utilizzare una funzione di fabbrica per allocare oggetti di diverse classi correlate da una singola funzione.
  • Se le tue classi hanno una classe base comune con un distruttore virtuale, puoi liberarle tutte usando la stessa funzione (o metodo statico).
  • Controlla il tuo programma con strumenti come purify (purtroppo molti $ / € / ...).

0

È possibile intercettare le funzioni di allocazione della memoria e vedere se ci sono alcune zone di memoria non liberate all'uscita dal programma (anche se non è adatto a tutte le applicazioni).

Può anche essere eseguito in fase di compilazione sostituendo gli operatori con nuove funzioni di allocazione della memoria e eliminazione e altre.

Ad esempio, controlla in questo sito [Debugging allocazione della memoria in C ++] Nota: esiste un trucco per eliminare l'operatore anche qualcosa del genere:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

È possibile memorizzare in alcune variabili il nome del file e quando l'operatore di eliminazione sovraccarico saprà da quale posizione è stato chiamato. In questo modo puoi avere la traccia di ogni cancellazione e malloc dal tuo programma. Alla fine della sequenza di controllo della memoria dovresti essere in grado di segnalare quale blocco allocato di memoria non è stato "cancellato" identificandolo con il nome del file e il numero di riga, suppongo che tu voglia.

Puoi anche provare qualcosa come BoundsChecker in Visual Studio che è piuttosto interessante e facile da usare.


0

Avvolgiamo tutte le nostre funzioni di allocazione con un livello che aggiunge una breve stringa nella parte anteriore e un flag di sentinella alla fine. Quindi, ad esempio, avresti una chiamata a "myalloc (pszSomeString, iSize, iAlignment); o new (" description ", iSize) MyObject (); che alloca internamente la dimensione specificata più spazio sufficiente per la tua intestazione e sentinella. , non dimenticare di commentarlo per build senza debug! Ci vuole un po 'più di memoria per farlo, ma i vantaggi superano di gran lunga i costi.

Ciò ha tre vantaggi: in primo luogo ti consente di tracciare facilmente e rapidamente quale codice perde, facendo ricerche rapide per il codice allocato in determinate "zone" ma non ripulito quando quelle zone avrebbero dovuto essere liberate. Può anche essere utile rilevare quando un limite è stato sovrascritto controllando che tutte le sentinelle siano intatte. Questo ci ha salvato numerose volte nel tentativo di trovare quei crash ben nascosti o passi falsi dell'array. Il terzo vantaggio è nel tracciare l'uso della memoria per vedere chi sono i grandi giocatori: una raccolta di alcune descrizioni in un MemDump ti dice quando il "suono" occupa molto più spazio di quanto ti aspettassi, per esempio.


0

C ++ è progettato per RAII. Penso che non esista davvero un modo migliore per gestire la memoria in C ++. Ma fai attenzione a non allocare blocchi molto grandi (come oggetti buffer) su ambito locale. Può causare overflow dello stack e, se si verifica un difetto nel controllo dei limiti durante l'utilizzo di quel blocco, è possibile sovrascrivere altre variabili o restituire gli indirizzi, il che porta a tutti i tipi di buchi di sicurezza.


0

Uno dei pochi esempi sull'allocazione e la distruzione in luoghi diversi è la creazione di thread (il parametro che si passa). Ma anche in questo caso è facile. Ecco la funzione / metodo che crea un thread:

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

Qui invece la funzione thread

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

Abbastanza facile, vero? Nel caso in cui la creazione del thread non riesca, la risorsa verrà liberata (eliminata) da auto_ptr, altrimenti la proprietà verrà passata al thread. Cosa succede se il thread è così veloce che dopo la creazione rilascia la risorsa prima di

param.release();

viene chiamato nella funzione / metodo principale? Niente! Perché "diremo" a auto_ptr di ignorare la deallocazione. La gestione della memoria C ++ è semplice, vero? Saluti,

Ema!


0

Gestisci la memoria nello stesso modo in cui gestisci altre risorse (handle, file, connessioni db, socket ...). GC non ti aiuterebbe neanche con loro.


-3

Esattamente un ritorno da qualsiasi funzione. In questo modo puoi fare deallocazione lì e non perdere mai.

Altrimenti è troppo facile fare un errore:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.

La tua risposta non corrisponde al codice di esempio qui? Sono d'accordo con la risposta "solo un ritorno" ma il codice di esempio mostra cosa NON fare.
simon,

1
Il punto di C ++ RAII è esattamente quello di evitare il tipo di codice che hai scritto. In C, questa è probabilmente la cosa giusta da fare. Ma in C ++, il tuo codice è difettoso. Ad esempio: cosa succede se si lancia un nuovo b ()? Perdita a.
Paercebal,
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.