2009-08-20 8 views
6

Eseguire valgrind o purificare sarebbe il passo successivo Ma mentre si scrive il codice stesso come si fa a garantire che non causerà alcuna perdita di memoria? È possibile garantire le seguenti cose: - 1: Numero di nuovo pari a cancellare 2: Aperto descrittore di file è chiusa o noCome assicurarsi che durante la scrittura del codice C++ stesso non causi alcuna perdita di memoria?

C'è qualche altra cosa?

+1

attento con la nuova opzione == delete. Supponiamo che tu abbia diversi posti in cui la memoria possa essere rilasciata, ciascuno con un controllo per nullo cioè se (p) cancelli p; 'Puoi finire con nuovo == N * cancella dove N è arbitrario. – ezpz

+3

'cancella 0' è un no-op, quindi non dovresti mai controllare se il tuo puntatore è nullo prima di cancellarlo. –

+0

Grazie Peter! Questo è il mio problema numero 1! "if (p) delete p;" dovrebbe essere riscritto come: "delete p; p = 0;" o qualcosa di simile. – Thomi

risposta

25

Utilizzare il linguaggio RAII ovunque è possibile

Utilizzare puntatori intelligenti, per esempio std :: auto_ptr dove appropriato. (Non utilizzare auto_prt in nessuna delle collezioni standard come non funzionerà come si pensa che sarà)

+1

+1 da me. Ma perché "ovunque tu possa" e non solo sempre (considerando i puntatori intelligenti una forma di RAII)? –

+2

@orsogufo, non mi piacciono le affermazioni assolute. Se dicessi "sempre" qualcuno potrebbe inventare una situazione in cui RAII non è appropriato (solo perché non ho pensato a una situazione del genere non significa che qualcun altro non l'abbia). – Glen

+0

Sì. se stai usando nuovo e cancella senza puntatori intelligenti, stai facendo qualcosa di sbagliato. – Thomi

7
+0

buon primo consiglio, ma fallisce nei cerchi – gimpf

+2

Sì, il secondo consiglio dovrebbe essere "Sii intelligente per te" :) – Aamir

+2

Usa boost :: weak_ptr <> per risolvere i cicli nel grafico di riferimento. – janm

0

Assicurarsi che la memoria condivisa creato da vostra applicazione è liberato se nessuno sta usando più, pulito up memory mapped files ...

In pratica, assicurati di pulire qualsiasi tipo di risorsa creata direttamente o indirettamente dalla tua applicazione. I descrittori di file sono solo un tipo di risorsa che l'applicazione può utilizzare durante il runtime.

1

Io uso sempre std::auto_ptr quando ho bisogno di creare un nuovo oggetto sull'heap.

std::auto_ptr<Foo> CreateFoo() 
{ 
    return std::auto_ptr<Foo>(new Foo()); 
} 

Anche se si chiama

CreateFoo() 

non colerà

+0

Tranne che non è possibile copiare std :: auto_ptr. Ciò significa che non può essere inserito nei contenitori standard, tra le altre cose. –

+0

Può, naturalmente, essere copiato, altrimenti non sareste in grado di scrivere funzioni che restituiscono auto_ptrs. –

0

se si effettua nessun albero o un grafico in modo ricorsivo nel codice per la vostra struttura dati forse mangiare tutto della vostra memoria.

14

Evitare di creare oggetti dinamicamente laddove possibile. I programmatori provenienti da Java e altri linguaggi simili spesso scrivono cose del genere:

string * s = new string("hello world"); 

quando dovrebbero hanno scritto:

string s = "hello world"; 

Allo stesso modo, creano collezioni di puntatori quando dovrebbero creare collezioni di valori. Ad esempio, se si dispone di una classe come questa:

class Person { 
    public: 
     Person(const string & name) : mName(name) {} 
     ... 
    private: 
     string mName; 
}; 

Piuttosto che scrivere codice come:

vector <Person *> vp; 

o anche:

vector <shared_ptr <Person> > vp; 

invece utilizzare i valori:

vector <Person> vp; 

Puoi facilmente aggiungere a tale ettore:

vp.push_back(Person("neil butterworth")); 

e tutta la memoria sia per Persona e il vettore è gestito per voi.Naturalmente, se avete bisogno di un insieme di tipi polimorfi, si consiglia di utilizzare puntatori (smart)

+2

Ciò rende effettivamente impossibile l'utilizzo del polimorfismo, rende difficile il passaggio dei dati e può contribuire a gonfiare. Non applicare questo consiglio all'ingrosso senza pensarci. –

+1

Hai letto l'ultima frase della mia risposta? –

+1

O il primo, vieni a quello - ho detto "dove possibile". –

0

Ci sono strumenti di analisi statica del codice disponibili che fanno questo genere di cose; wikipedia è un buon posto per iniziare a cercare. Fondamentalmente, al di fuori di fare attenzione e scegliere i contenitori corretti, non è possibile garantire il codice che si scrive, quindi la necessità di strumenti come valgrind e gdb.

4

Ridurre al minimo le chiamate a nuovo utilizzando i contenitori STL per la memorizzazione dei dati.

1

I passaggi fondamentali sono due:

In primo luogo, essere consapevoli del fatto che ogni nuovo richiede una eliminazione. Quindi, quando si utilizza il nuovo operatore, si aumenta la consapevolezza di ciò che l'oggetto farà, di come sarà usato e di come sarà gestita la sua vita.

In secondo luogo, fare in modo che non avete mai sovrascritto un puntatore. Puoi farlo usando una classe puntatore intelligente invece dei puntatori grezzi, ma se sei assolutamente sicuro di non usarla mai con la conversione implicita. (un esempio: usando la libreria MSXML, ho creato un puntatore intelligente CCOMPtr per contenere i nodi, per ottenere un nodo si chiama il metodo get_Node, passando l'indirizzo del puntatore intelligente, che aveva un operatore di conversione che restituiva il tipo di puntatore sottostante. , questo significava che se il puntatore intelligente conteneva già dati, i dati dei membri venivano sovrascritti, perdendo il nodo precedente).

Penso che quei 2 casi siano i tempi in cui si potrebbe perdere memoria. Se si utilizza direttamente il puntatore intelligente, non consentendo mai l'esposizione dei propri dati interni, si è al sicuro da quest'ultimo problema. Se si avvolge tutto il codice che usa new e delete in una classe (cioè usando RAII), allora si è abbastanza al sicuro dal primo.

Evitare le perdite di memoria in C++ è molto facile se si fa quanto sopra.

5
  1. Usa RAII
  2. Nascondi copia predefinite ctors, operator =() in ogni classe, a meno che a) la classe è banale e utilizza solo tipi nativi e non si sa lo sarà sempre SO b) si definire in modo esplicito il proprio

il 1) Raii, l'idea è quella di avere le eliminazioni avvengono automaticamente, se vi trovate a pensare "ho appena chiamato nuovo, avrò bisogno di ricordarsi di chiamare cancellare da qualche parte", allora si Stai facendo qualcosa di sbagliato. L'eliminazione dovrebbe essere a) automatica o b) essere inserita in un dtor (e quale dtor dovrebbe essere ovvio).

On 2) Nascondere i valori predefiniti. Identificare i criptofili predefiniti, ecc. Può essere un incubo, la cosa più semplice è evitarli nascondendoli. Se si dispone di un oggetto generico "root" che tutto eredita da (può essere utile per il debug/profiling) nascondere i valori predefiniti qui, quindi quando un qualcosa tenta di assegnare/copiare una classe ereditaria il barfs del compilatore perché il ctor non è ecc. disponibile sulla classe base.

+2

boost :: noncopyable è molto utile per # 2 – jalf

+0

@jalf: Grazie, affronterà boost se avrò bisogno di fare di nuovo un serio C++. Non ho usato C++ in rabbia in 5 anni o più ora :) –

+0

In suvated, ma perché nascondere operatore *()? –

1

Due semplici regole pratiche:

  • mai chiamata delete esplicitamente (al di fuori di una classe Raii, che è). Ogni allocazione di memoria dovrebbe essere responsabilità di una classe RAII che chiama delete nel distruttore.
  • Quasi mai chiamare new esplicitamente.Se lo fai, dovresti immediatamente racchiudere il puntatore risultante in un puntatore intelligente, che diventa proprietario dell'allocazione e funziona come sopra.

In proprie classi RAII, due errori più comuni sono:

  • mancata per gestire la copia corretta: chi assume la proprietà della memoria se l'oggetto viene copiato? Creano una nuova allocazione? Implementa sia il costruttore di copia che l'operatore di assegnazione? Quest'ultimo maneggia l'autoassegnazione?
  • Mancata considerazione della sicurezza delle eccezioni. Cosa succede se viene lanciata un'eccezione durante un'operazione (un incarico, per esempio)? L'oggetto torna a uno stato coerente? (dovrebbe sempre fare questo, non importa cosa) Torna allo stato che aveva prima dell'operazione? (dovrebbe farlo quando possibile) std::vector deve gestire questo, per esempio, durante push_back. Potrebbe causare il ridimensionamento del vettore, che significa 1) un'allocazione di memoria che può essere lanciata, e 2) tutti gli elementi esistenti devono essere copiati, ognuno dei quali può essere lanciato. Anche un algoritmo come std::sort deve occuparsene. Deve chiamare un comparatore fornito dall'utente, che potrebbe potenzialmente lanciare anche! se ciò accade, la sequenza è rimasta in uno stato valido? Gli oggetti temporanei vengono distrutti in modo pulito?

Se si gestiscono i due casi precedenti nelle classi RAII, è praticamente impossibile che perdano memoria. E se si utilizzano le classi RAII per avvolgere tutte le allocazioni di risorse (allocazioni di memoria, handle di file, connessioni di database e qualsiasi altro tipo di risorsa che deve essere acquisito e rilasciato), l'applicazione non può perdere memoria.

+0

Utilizza i puntatori intelligenti standard e Boost durante la creazione di queste classi. Ti farà risparmiare lavoro e bug sottili. –

+1

Naturalmente. Quando sono disponibili strumenti standard per fare ciò che ti serve, * usali *. Ma continuerei a sostenere che è importante essere consapevoli di questi problemi sottili in primo luogo. – jalf

3

Sono con Glen e Jalf per quanto riguarda RAII in ogni occasione.

IMHO si dovrebbe mirare a scrivere codice completamente privo di cancellazioni. L'unica "eliminazione" esplicita dovrebbe essere nelle implementazioni della classe smart pointer. Se ti senti di voler scrivere un "cancella", vai a cercare un tipo di puntatore intelligente appropriato. Se nessuno di quelli "standard del settore" (boost ecc.) Si adatta e ci si trova a voler scrivere qualcosa di nuovo bizzarro, è probabile che la propria architettura sia interrotta o che in futuro ci saranno delle difficoltà di manutenzione.

Ho da lungo tempo dichiarato che "delete" esplicito è per la gestione della memoria che cosa "goto" è per il controllo del flusso. Maggiori informazioni su questo in this answer.

+0

Direi lo stesso per il nuovo. Per molte risorse, preferisco scrivere una classe RAII completa che crei la risorsa e la rilasci. Un puntatore intelligente è responsabile solo dell'eliminazione, quindi richiede la creazione della risorsa altrove, che è un po 'soggetta a errori. – jalf

+0

Interessante. Ho ottenuto buoni risultati da "eliminare le purghe" sul vecchio codice precedente, ma non ho mai pensato di estenderlo anche a quello nuovo, probabilmente perché inizialmente lo stavo provando dal punto di vista del codice C++ che sembrava più "simile a Java" . I modelli variadici del sospetto C++ 0x sarebbero utili da qualche parte per semplificare il passaggio degli argomenti del costruttore in tipi di puntatori intelligenti che sarebbero anche responsabili del richiamo di nuovi. – timday

+1

C'è una funzione boost :: make_shared in boost che creerà un boost :: shared_ptr per te, inoltrando gli argomenti passati nel costruttore del tipo: 'boost :: shared_ptr ptr = make_shared (Arg1, Arg2, ... ' –

0

Incorporare l'unità valgrind e il test del sistema nelle prime fasi del ciclo di sviluppo e utilizzarlo in modo coerente.

+1

È molto meglio non avere problemi di gestione della memoria. –

Problemi correlati