2012-10-10 10 views
6

sto cercando di stampare su quale linea AddRef e il rilascio è called.Here è codiceRilevamento perdita di memoria in riferimento contato oggetti

Nel codice qui sotto ho creato sulla classe ReferenceCount cui funzionalità principale per aumentare e diminuire conteggio refernce. La classe Referencemanager tiene traccia del conteggio dei riferimenti e cancella l'oggetto quando raggiunge 0.

Test1 è una classe di test. In generale sto creando il puntatore Test1 e lo avvolgo con la classe CReferenceManager. Ora durante la creazione della classe CReferenceManager viene chiamato AddRef e viene chiamata la versione di distruzione.

Se è presente una perdita di memoria, sarebbe più facile rilevare se è possibile stampare i numeri FILE e LINE quando AddRef e Release sono chiamati con conteggi di riferimento in quel punto.

Se esiste un modo per stampare FILE e il numero LINE da cui vengono chiamati AddRef e Release. Un modo è che posso sovrascrivere AddRef e Release nelle classi derivate e FILE e LINE numeri prinf

domande
//ReferenceCount.h 
#include <string> 
#include <Windows.h> 

using namespace std; 
class CReferenceCount 
{ 
public: 
    CReferenceCount(); 
    virtual ~CReferenceCount(); 
    virtual void AddRef(); 
    virtual bool Release(); 


private: 
    LONG m_ref; 

}; 


// RefCount.cpp 
// 

#include "stdafx.h" 
#include "ReferenceCount.h" 


CReferenceCount::CReferenceCount():m_ref(0) 
{ 
    AddRef(); 

} 

CReferenceCount::~CReferenceCount() 
{ 
} 

void CReferenceCount::AddRef() 
{ 
    InterlockedIncrement(&m_ref); 
} 

bool CReferenceCount::Release() 
{ 
    if (InterlockedDecrement(&m_ref) == 0) 
    { 
     delete this; 
     return true; 
    } 

    return false; 
} 



//ReferenceManager.h 
#include <string> 
#include <Windows.h> 

using namespace std; 
class CReferenceCount 
{ 
public: 
    CReferenceCount(); 
    virtual ~CReferenceCount(); 
    virtual void AddRef(); 
    virtual bool Release(); 


private: 
    LONG m_ref; 

}; 

//test.cpp 
#include "stdafx.h" 
#include "ReferenceCount.h" 
#include "RefManager.h" 
#include <iostream> 
using namespace std; 

class Test1: public CReferenceCount 
{ 
public: 
    Test1(){} 
    ~Test1(){} 

private : 
    int m_i; 
}; 

void main() 
{ 
    Test1 *pTest= new Test1(); 
    CReferenceManager<Test1> testRef(pTest); 

} 

similare ho postato finding who creates object via smart pointer Design pattern to detect memory leaks for reference counted smart pointers

ma non delle risposte dare spiegazione giusta per affrontare questo proble

+9

'cancella questo;' OMG !!! –

+0

usi il puntatore intelligente per chiamare AddRef/Release o li chiami manualmente? Se li chiami manualmente, ti consiglio vivamente di non farlo. –

+0

un modello sarebbe una soluzione migliore; il modello può essere creato per adattarsi a ogni oggetto, proprio come un std :: shared_ptr –

risposta

6

L'unico modo è definire le macro per chiamare AddRef e Release, poiché non è possibile che le funzioni sappiano internamente da dove vengono chiamate. Quindi potresti usare qualcosa del genere.

#define RELEASE(obj) cout << __LINE__ << ":" << __FILE__ << endl; (obj).Release(); 

Inoltre, diversi compilatori hanno diverse macro predefinite; se la portabilità è una preoccupazione, è qualcosa che dovresti esaminare quando scrivi il codice come sopra. MSDN reference (2003)

Considerati i vostri commenti di seguito, potrei offrire un'altra soluzione un po 'hacker. Potresti non essere in grado di vedere dove viene pubblicato il tuo riferimento, ma puoi ottenere maggiori informazioni su dove è stato creato e che non vengono rilasciati correttamente.

template <typename T> 
struct CReferenceManager 
{ 
    CReferenceManager(const T & _obj, const string & _file, int _line) : mObj(_obj), mFile(_file), mLine(_line) 
    { 
     cout << "Constructing from " << _file << ":" << _line << endl; 
     CReferenceManager::sObjects[make_pair(mFile, mLine)]++; 
     mObj.addRef(); 
    } 

    ~CReferenceManager() 
    { 
     cout << "Destructing object created at " << mFile << ":" << mLine << endl; 
     CReferenceManager::sObjects[make_pair(mFile, mLine)]--; 
     mObj.Release(); 
    } 

    static map<pair<string, int>, int> sObjects; 
    string mFile; 
    int mLine; 
    T obj; 
} 

int main() 
{ 
... 
    // Cycle through sObjects before return, note any unreleased entries 
    return 0; 
} 

Nota: questo è solo uno pseudo-codice; Dubito che compili o lavori fuori dalla scatola!

+0

Questo è quello che ho consigliato qui, ieri: http://stackoverflow.com/a/12806087/241536 –

+0

Più o meno .. –

+0

Se si utilizza questa macro, verrà stampato FILE come RefManager e non test.cpp.Il mio scopo è stampare test.cpp e il relativo numero di riga da cui viene richiamato Release. – anand

2

C'è un modo per farlo, ma prima lascia che ti chieda una cosa. Perché vuoi gestire i riferimenti a mano e fornire un'opportunità per perdite di memoria? puoi usare facilmente boost::intrusive_ptr per fare il lavoro per te? (se non vuoi la spinta, non ci sono problemi, vedi l'implementazione di intrusive_ptr e implementa la tua classe o semplicemente copialo nel tuo file) e poi non lo fai Ho una perdita di memoria per cercarlo !!

Ma come una risposta per la tua domanda si potrebbe avere 2 AddRef/Release uno per la versione di debug e un altro per il rilascio e si dovrebbe aggiungere AddRef posizioni ad una struttura come std::stack e Release farli scoppiare da stack e al fine di vedere quanto i riferimenti dalle posizioni delle streghe sono rimasti in pila! ma se ciò è per l'implementazione COM, ricordare che COM può chiamare più volte AddRef e quindi rimuoverli in un secondo momento e quindi non è possibile capire quale non ha uno Release corrispondente.

5

Non si dovrebbe mai allocare o rilasciare in modo esplicito riferimenti nel proprio codice, in modo da salvare il file sorgente e la linea dove i riferimenti sono incrementati o decrementati non sta per aiutarti a tutti, dal momento che quelli sarà (dovrebbe !) sempre all'interno del codice di gestione del conteggio dei riferimenti.

Non è stato incluso il codice sorgente nella classe CReferenceManager, ma in base alla descrizione si tratta di un wrapper per un oggetto conteggiato referenziato. È corretto? La corretta applicazione di questo oggetto CReferenceManager dovrebbe assicurare che:

  • un costruttore che prende una naked negozi puntatore il puntatore e non cambia il conteggio dei riferimenti (dal momento che la classe CReferenceCount crea oggetto con un riferimento)
  • riferimento è sempre decrementato nel distruttore
  • riferimento viene incrementata nel costruttore di copia
  • viene incrementato riferimento per l'oggetto lato destro, e riferimento per l'oggetto lato sinistro viene decrementato in operatore di assegnazione
  • esistono metodi di riferimento incremento/decremento esplicita vanno esposti
  • dell'operatore metodo ->() deve restituire il puntatore all'oggetto
  • ci dovrebbe essere un modo diretto per staccare l'oggetto contato riferimento da un'istanza CReferenceManager che possiede . L'unico modo è tramite l'assegnazione di un nuovo oggetto di conteggio di riferimento.

Inoltre, che ci si vuole rendere i AddRef() e Release() metodi nella tua classe CReferenceCount privato, e renderli accessibili solo alla classe CReferenceManager tramite classe amicizia.

Se si seguono le regole precedenti nella classe CReferenceManager, è possibile evitare perdite o altri problemi di memoria assicurandosi che tutti accedano all'oggetto tramite un wrapper CReferenceManager allocato nello stack. In altre parole:

Per creare un nuovo oggetto conteggiato di riferimento, è passato un oggetto appena creato (con un riferimento) a uno stack assegnato all'oggetto CReferenceManager. Esempio:

CReferenceManager<Test1> testRef(new Test1()); 

passare l'oggetto come argomento di un'altra funzione o metodo, passare sempre un oggetto CReferenceManager per valore (non per riferimento, e non per puntatore). Se lo fai in questo modo, il costruttore di copie e il distruttore si prenderanno cura di mantenere i conteggi di riferimento per te. Esempio:

void someFunction(CReferenceManager<Test1> testObj) 
{ 
    // use testObj as if it was a naked pointer 
    // reference mananagement is automatically handled 
    printf("some value: %d\n", testObj->someValue()); 
} 

int main() 
{ 
    CReferenceManager<Test1> testRef(new Test1()); 
    someFunction(testRef); 
} 

Se è necessario attenersi all'oggetto contato di riferimento in un contenitore, quindi inserire un wrapper CReferenceManager per valore (non il suo puntatore, e non il puntatore nuda dell'oggetto). Esempio:

std::vector< CReferenceManager<Test1> > myVector; 
CReferenceManager<Test1> testRef(new Test1()); 
myVector.push_back(testRef); 
myVector[0]->some_method(); // invoke the object as if it was a pointer! 

Credo che se si seguono rigorosamente le regole di cui sopra gli unici problemi che si possono trovare bug nell'implementazione di conteggio di riferimento.

Un'implementazione di esempio che segue queste regole è in this page, sebbene tale soluzione non abbia alcun supporto per la protezione multi-thread.

Spero che questo aiuti!

1

Il principio del conteggio dei riferimenti consiste nell'aumentare il contatore quando l'utente si collega all'oggetto e diminuire quando interrompe il collegamento.

quindi bisogna:

  • manipolare i puntatori intelligenti, non puntatori per fare aumentare/diminuire trasparente
  • costruttore di sovraccarico di copia e assegnare operatore del smart_pointer

exemple simbolico:

  • A a = new A(); refcount = 0, nessuno lo usa
  • Link<A> lnk(a); refcount = 1
  • obj.f(lnk); obj negozi LNK, refcount = 2
  • questo metodo può rendimenti in quanto la proprietà è stato trasferito al obj

Quindi, dare un'occhiata a parametro di passaggio (può fare copie automatiche) e alla copia in oggetti estranei.

Esiste un buon tutorial su quello nelle nebulose CORBA.

È inoltre possibile vedere ACE o ICE o 0MQ.

2

Per i progetti in cui sono coinvolto ho avuto esigenze simili. Abbiamo la nostra classe del modello di puntatore intelligente e di volta in volta le perdite di memoria sono apparse a causa di riferimenti circolari.

Per sapere quale smart-pointer che fa riferimento a un oggetto trapelato è ancora vivo (2 o più), compiliamo le origini con una definizione speciale del pre-processore che abilita il codice di debug speciale nell'implementazione di smart-pointer. Puoi dare un'occhiata al nostro smart-pointer class.

In sostanza, ogni oggetto con contatori intelligenti e riferimento contato ottiene un ID univoco. Quando otteniamo l'id per l'oggetto trapelato (solitamente usando valgrind per identificare la posizione di origine dell'allocazione di memoria per l'oggetto fuoriuscito), usiamo il nostro codice di debug speciale per ottenere tutti gli id ​​di puntatore intelligente che fanno riferimento all'oggetto. Quindi usiamo un file di configurazione in cui annotare gli id ​​del puntatore intelligente e all'avvio successivo dell'applicazione, questo file viene letto dal nostro strumento di debug che quindi sa per quale istanza di puntatore intelligente appena creata deve attivare un segnale per l'accesso al debugger. Questo rivela la traccia dello stack in cui è stata creata l'istanza di puntatore intelligente.

Certamente, questo implica un po 'di lavoro e potrebbe essere redditizio solo per progetti più grandi.

Un'altra possibilità sarebbe quella di registrare una traccia stack all'interno del proprio metodo AddRef in fase di esecuzione. Dai un'occhiata alla mia classe ctkBackTrace per creare una traccia dello stack in fase di runtime. Dovrebbe essere facile sostituire i tipi specifici Qt con tipi STL standard.

1

Un modo per fare quello che hai chiesto, è quello di passare AddRef e Release queste informazioni utilizzando qualcosa di simile:

void CReferenceCount::AddRef(const char *file=0, int line=-1) { if (file) cout << "FILE:" << file; if (line>0) count << " LINE: " << line; .... do the rest here ... } 

Poi, quando si chiama la funzione, è possibile utilizzare una macro simile a quello che Rollie suggerito sopra , in questo modo:

#define ADDREF(x) x.AddRef(__FILE__, __LINE__) 

questa passerà il file e la linea in cui viene effettuata la chiamata, che credo sia quello che hai chiesto. Puoi controllare cosa vuoi fare con le informazioni all'interno dei metodi. Stamparli, come ho fatto sopra, è solo un esempio. Potresti voler raccogliere più informazioni oltre a questo e registrarlo su un altro oggetto, in modo da avere una cronologia delle tue chiamate, scriverle in un file di registro, ecc. Puoi anche passare più informazioni dai punti di chiamata che solo il file e linea, in base al tipo e al livello di tracciamento di cui hai bisogno. I parametri predefiniti consentono anche di utilizzarli senza passare nulla (con una semplice macro ridefinizione), solo per vedere come si comporterà la versione finale, con il sovraccarico di due stack stack e due controlli di condizione.

+0

Come sottolineato in un commento alla risposta di Rollie, questo non funzionerà poiché l'OP deve inserire la macro chiamata nella sua classe template CReferenceManager. Quindi la macro stamperà sempre RefManager come nome del file. – Sascha

+0

Ehm, no. funzionerà se la macro si trova in un file di intestazione e verrà utilizzata in molti altri file cpp. – DNT

+0

Lo spazio qui non è sufficiente per incollare un'esecuzione di esempio, ma posso aggiungere una risposta se qualcuno è interessato. – DNT

1

Risposta breve: si consiglia di utilizzare le idee che gli altri pubblicati, vale a dire che fanno uso di macro ADD/release e che passano il predefinito _ _ FILE _ _ e _ _ LINE _ _ macro che il compilatore fornisce alla classe di monitoraggio.

Risposta leggermente più lunga: è anche possibile utilizzare la funzionalità che consente di camminare nello stack e vedere chi ha chiamato la funzione, che è leggermente più flessibile e pulita rispetto all'utilizzo di macro, ma quasi certamente più lenta.

Questa pagina mostra come ottenere ciò quando si utilizza GCC: http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/.

In Windows è possibile utilizzare alcuni elementi intrinseci del compilatore con funzionalità di ricerca dei simboli. Per i dettagli, consulta: http://www.codeproject.com/tools/minidump.asp

Nota che in entrambi i casi il tuo programma dovrebbe includere almeno alcuni simboli per farlo funzionare.

A meno che non si abbiano requisiti speciali per farlo in fase di esecuzione, suggerirei di controllare la risposta breve.