web-dev-qa-db-de.com

Wie man Speicherzuordnungen in C++ verfolgt (insbesondere neu/löschen)

Wie kann ich die Speicherzuordnungen in C++ verfolgen, insbesondere die von new/delete. Für ein Objekt kann ich den operator new leicht überschreiben, aber ich bin nicht sicher, wie ich alle Zuordnungen global überschreiben soll, damit sie meine benutzerdefinierte new/delete durchlaufen. Dies sollte kein großes Problem sein, aber ich bin mir nicht sicher, wie das gemacht werden soll (#define new MY_NEW?).

Sobald dies funktioniert, würde ich davon ausgehen, dass es ausreicht, eine Karte irgendwo mit einem Zeiger/einer Position der Zuordnung zu haben, damit ich alle Zuordnungen verfolgen kann, die derzeit "aktiv" sind, und - am Ende der Anwendung - nach Zuordnungen suchen kann die nicht befreit wurden.

Nun, das scheint wieder so zu sein, wie es sicherlich schon mehrmals gemacht wurde, also eine gute Bibliothek da draußen (am besten eine tragbare)?

28
Anteru

Ich würde Ihnen empfehlen, valgrind für Linux zu verwenden. Es wird nicht freigegebenen Speicher abfangen, unter anderem Fehler wie das Schreiben in nicht zugewiesenen Speicher. Eine andere Option ist Schmutzfänger, der Sie auch über nicht freigegebenen Speicher informiert. Verwenden Sie -fmudflap -lmudflap options mit gcc und starten Sie Ihr Programm mit MUDFLAP_OPTIONS=-print-leaks ./my_program.

Hier ist ein sehr einfacher Code. Es ist nicht für anspruchsvolles Tracking geeignet, sondern soll Ihnen zeigen, wie Sie es im Prinzip tun würden, wenn Sie es selbst implementieren würden. Etwas wie dieses (ausgelassenes Zeug, das den registrierten new_handler und andere Details aufruft).

template<typename T>
struct track_alloc : std::allocator<T> {
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::size_type size_type;

    template<typename U>
    struct rebind {
        typedef track_alloc<U> other;
    };

    track_alloc() {}

    template<typename U>
    track_alloc(track_alloc<U> const& u)
        :std::allocator<T>(u) {}

    pointer allocate(size_type size, 
                     std::allocator<void>::const_pointer = 0) {
        void * p = std::malloc(size * sizeof(T));
        if(p == 0) {
            throw std::bad_alloc();
        }
        return static_cast<pointer>(p);
    }

    void deallocate(pointer p, size_type) {
        std::free(p);
    }
};

typedef std::map< void*, std::size_t, std::less<void*>, 
                  track_alloc< std::pair<void* const, std::size_t> > > track_type;

struct track_printer {
    track_type * track;
    track_printer(track_type * track):track(track) {}
    ~track_printer() {
        track_type::const_iterator it = track->begin();
        while(it != track->end()) {
            std::cerr << "TRACK: leaked at " << it->first << ", "
                      << it->second << " bytes\n";
            ++it;
        }
    }
};

track_type * get_map() {
    // don't use normal new to avoid infinite recursion.
    static track_type * track = new (std::malloc(sizeof *track)) 
        track_type;
    static track_printer printer(track);
    return track;
}

void * operator new(std::size_t size) throw(std::bad_alloc) {
    // we are required to return non-null
    void * mem = std::malloc(size == 0 ? 1 : size);
    if(mem == 0) {
        throw std::bad_alloc();
    }
    (*get_map())[mem] = size;
    return mem;
}

void operator delete(void * mem) throw() {
    if(get_map()->erase(mem) == 0) {
        // this indicates a serious bug
        std::cerr << "bug: memory at " 
                  << mem << " wasn't allocated by us\n";
    }
    std::free(mem);
}

int main() {
    std::string *s = new std::string;
        // will print something like: TRACK: leaked at 0x9564008, 4 bytes
}

Wir müssen unseren eigenen Allokator für unsere Karte verwenden, da der Standard unseren überschriebenen Operator new verwendet, was zu einer unendlichen Rekursion führen würde.

Stellen Sie sicher, dass Sie beim Überschreiben von operator new die Zuordnung verwenden, um Ihre Zuordnungen zu registrieren. Das Löschen des Speichers, der durch Platzierungsformen von new zugewiesen wurde, verwendet auch diesen Löschoperator. Daher kann es schwierig werden, wenn ein Code, von dem Sie nicht wissen, dass er den Operator new überladen hat, der Ihre Map nicht verwendet, da der Operator delete Ihnen mitteilt, dass er nicht zugewiesen wurde und Verwenden Sie std::free, um den Speicher freizugeben.

Beachten Sie auch, wie Pax auch für seine Lösung betont hat, dass dies nur Lecks anzeigt, die durch Code verursacht werden, der unseren eigenen definierten Operator new/delete verwendet. Wenn Sie sie verwenden möchten, fügen Sie ihre Deklaration in einen Header ein und fügen Sie sie in alle Dateien ein, die überwacht werden sollen.

Um genau zu sein, benutze Valgrinds Massivwerkzeug. Im Gegensatz zu Memcheck befasst sich Massif nicht mit der illegalen Verwendung von Speicher, sondern mit der Verfolgung von Zuweisungen über einen längeren Zeitraum. Es ist eine gute Aufgabe, die Heap-Speichernutzung eines Programms effizient zu messen. Das Beste daran ist, dass Sie keinen Code schreiben müssen. Versuchen:

http://valgrind.org/docs/manual/ms-manual.html

Oder wenn Sie wirklich ungeduldig sind:

valgrind --tool=massif <executable> <args>
ms_print massif.out.<pid> | less

Auf diese Weise erhalten Sie eine grafische Darstellung der Zuweisungen über die Zeit und können zurückverfolgen, wo die großen Zuweisungen aufgetreten sind. Dieses Tool läuft am besten unter Linux, ich weiß nicht, ob es einen Windows-Varient gibt. Es funktioniert unter OS X.

Viel Glück!

24
Necro

Sie können den Code unter http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml mit den folgenden Änderungen verwenden: Der angegebene Code funktioniert nur, wenn Sie eine große Honkin-Quelldatei haben. Ich habe dies für eine andere Frage auf SO ( hier ) aussortiert.

Zunächst nehmen Sie mit don't change stdafx.h Ihre Änderungen in Ihren eigenen Dateien vor.

Erstellen Sie eine separate Header-Datei mymemory.h und fügen Sie beispielsweise Ihre Funktionsprototypen hinzu (beachten Sie, dass dies kein body enthält):

inline void * __cdecl operator new(unsigned int size,
    const char *file, int line);

Fügen Sie in diesen Header auch die anderen Prototypen für AddTrack (), DumpUnfreed () usw. sowie die Anweisungen #defines, typedef und extern ein:

extern AllocList *allocList;

Fügen Sie dann in eine neue Datei mymemory.cpp (die auch die Datei mymemory.h enthält) die tatsächliche Definition von allocList zusammen mit allen tatsächlichen Funktionen (nicht nur den Prototypen) ein und fügen Sie diese Datei Ihrem Projekt hinzu.

Dann #include "mymemory.h" in jeder Quelldatei, in der Sie den Speicher verfolgen müssen (wahrscheinlich alle). Da die Header-Datei keine Definitionen enthält, werden während der Verknüpfung keine Duplikate erstellt, und da die Deklarationen vorhanden sind, werden auch keine undefinierten Referenzen angezeigt.

Beachten Sie, dass hierdurch keine Speicherverluste in nicht kompiliertem Code (z. B. Bibliotheken von Drittanbietern) nachverfolgt werden, Sie jedoch über Ihre eigenen Probleme informiert werden sollten.

9
paxdiablo

Nun, Sie können die globalen Operatoren new und delete erneut implementieren, um die gewünschte Funktionalität zu erhalten. Ich rate jedoch davon ab, es sei denn, dies ist die einzige Möglichkeit, die Speicherzuweisung zu verfolgen, beispielsweise aufgrund von Einschränkungen auf Ihrer Plattform.

Speicher-Debugger sind für die meisten gängigen Entwicklungsplattformen verfügbar. Werfen Sie einen Blick auf PurifyPlus für eine kommerzielle Lösung, die unter Windows und verschiedenen Unixen funktioniert, oder valgrind für eine Open Source-Lösung, die unter Linux (und möglicherweise auch unter anderen Betriebssystemen) funktioniert, die ich jedoch bisher nur verwendet habe Linux).

Wenn Sie beabsichtigen, die globalen Operatoren zu ersetzen, lesen Sie diesen Artikel .

7
Timo Geusch

Wenn Sie unter Windows entwickeln, hilft das kostenlose Tool DebugDiag beim Auffinden von Speicher und beim Behandeln von Lecks.

Sie müssen Ihr Programm nicht erweitern, damit DebugDiag funktioniert.

http://www.Microsoft.com/downloads/details.aspx?FamilyID=28BD5941-C458-46F1-B24D-F60151D875A3&displaylang=de

Obwohl es nicht das einfachste oder intuitivste Programm ist! Stellen Sie sicher, dass Sie auf Google nach Tutorials und Anleitungen zur Verwendung suchen.

3
Ashley Davis

Für unsere Windows-Plattform-C++ -Projekte verwende ich VLD, den visuellen Lecksucher, der fast zu einfach zu implementieren ist, um Speicherlecks zu verfolgen und zu melden, wenn Ihre Anwendung beendet wird - das Beste ist, dass er kostenlos ist und die Quelle verfügbar ist. Das System kann so eingerichtet werden, dass es auf verschiedene Arten Berichte erstellt (Datenträgerprotokollierung, IDE, XML usw.). Es war von unschätzbarem Wert, um Lecks in Windows-Diensten zu erkennen, die immer eine Herausforderung beim Debuggen darstellen. Wenn Sie also nach einer tragbaren Lösung suchen, können Sie sich natürlich die Quelle anzeigen lassen, um eine Anleitung zu erhalten. Ich hoffe es hilft.

So zitieren Sie die Site:

Dies ist eine sehr effektive Methode, um Speicherlecks in C/C++ - Anwendungen schnell zu diagnostizieren und zu beheben.

http://dmoulding.googlepages.com/vld

3
Damien

Unter Linux gibt es mindestens zwei traditionelle Methoden:

  • malloc () und free () (und andere speicherbezogene Funktionen) sind schwache Symbole, was bedeutet, dass Sie sie einfach neu implementieren können und Ihre Versionen verwendet werden. Ein Implementierungsbeispiel finden Sie unter Elektrozaun.
  • Mit der Umgebungsvariablen LD_PRELOAD können Sie Symbole (schwache und starke) in gemeinsam genutzten Bibliotheken mit den Symbolen in den Bibliotheken überschreiben, die in der Umgebungsvariablen LD_PRELOAD enthalten sind. Wenn Sie mit malloc (), free () und friends eine gemeinsam genutzte Bibliothek kompilieren, sind Sie bereit. Auch hier zeigt der Elektrozaun dies.

Als solches erfassen Sie nicht nur neue und löschen, sondern auch die Speicherzuweisungsfunktionen im C-Stil. Ich habe dies unter Windows noch nicht getan, aber ich habe Methoden gesehen, um neu zu schreiben, wie DLLs dort auch verknüpft werden (obwohl ich mich erinnere, dass sie etwas ungeschickt waren).

Beachten Sie jedoch, dass ich, abgesehen von der Tatsache, dass dies interessante Techniken sind, empfehlen würde, Valgrind zu verwenden, um das zu tun, was Sie vor allem anderen wollen.

3
user52875

Sie beantworten Ihre Frage nicht direkt, aber wenn Sie wirklich nur eine Liste der durchgesickerten Heap-Objekte am Ende des Programms erhalten möchten, können Sie das Programm einfach mit valgrind ausführen.

Für MS VS können Sie mit dem Debug CRT Heap spielen. Nicht so einfach wie Valgrind, ein bisschen zu viel, um es hier zu erklären, aber vielleicht tun Sie, was Sie wollen.

1
gimpf
1
Torleif

Wenn Sie dies als Programmierübung durchführen möchten, erhalten Sie möglicherweise viel mehr Einsicht, wenn Sie stattdessen Ihre eigenen Smart-Pointer-Klassen schreiben und diese konsequent in diesem Projekt (oder Modul eines Projekts) verwenden.

0
hemflit

Überprüfen Sie diesen winzigen praktischen Code, verwenden Sie jetzt anstelle von newNEW und verfolgen Sie alle Zuordnungen im Konstruktor NewHelper:

#include <iostream>

class NewHelper
{
   private :
    void* addr = nullptr;
       public :
       NewHelper(void * addr_)
       {
          addr = addr_;
          std::cout<<addr<<std::endl;
       }
       template <class T>
       operator T ()
       {
           return (T)addr;
       }
};
#define NEW (NewHelper)(void*)new
int main()
{
  int * i = NEW int(0);
 return 0;
}
0
Dhia Hassen

Wenn ich ein Tool benötige, beginne ich normalerweise mit dem, was meine Compiler-/Standardbibliothek bietet.

  • Wenn Sie glibc verwenden, können Sie mtrace verwenden. Es installiert einen globalen Hook, der jede glibc-Speicherzuweisungsfunktion protokolliert (malloc, realloc, memalign, free und alles, was darauf implementiert ist, wie neu/löschen).
  • Wenn Sie Microsoft CRT verwenden, können Sie sich CRT Debug Heap Details ansehen. Es gibt Beispiele für die Installation der Debug-Version von Speicherzuweisungsfunktionen, das Abrufen von Heap-Statistiken, das Auffinden von Speicherlecks usw.
0
Dmitrius

Wenn Sie unter Linux entwickeln, ist Valgrind, insbesondere das Massif-Tool, eines der besten Tools dafür (z. B. das Erkennen von Speicherlecks und das Verfolgen von Zuweisungen an bestimmten Stellen des Codes). Der einzige Nachteil ist, dass das Programm langsamer (oder viel langsamer) ausgeführt wird, sodass es nur zum Debuggen nützlich ist.

0
jpalecek

Sie können Ihrer Lösung eine Header-Datei (MemTracker.h) hinzufügen, die in dieserlinkangegeben ist, um die Speicherzuordnung zu verfolgen/Freigabe in C und C++. Es wird angezeigt, ob ein Speicherverlust vorliegt und welche Codezeile dafür verantwortlich ist.

0
hnl

Mir ist aufgefallen, dass sich viele andere Antworten darauf konzentrieren, welche Tools Sie verwenden können. Ich habe einige von ihnen benutzt und sie helfen sehr.

Aber als Programmierübung müssen Sie, wenn Sie mit c ++ arbeiten, das globale Neue und Löschen sowie Malloc, Free und Realloc überschreiben. Sie würden denken, dass nur das Überschreiben von new und delete ausreichen würde, aber der std :: string und andere Klassen werden wahrscheinlich malloc und insbesondere realloc verwenden.

Sobald Sie dies eingerichtet haben, können Sie Header hinzufügen, um nach Speicherüberschreibungen zu suchen, Stapelverfolgungen pro Zuordnung aufzuzeichnen usw.

Alles in allem würde ich empfehlen, dass Sie sich für eines der hier genannten Tools entscheiden, aber es könnte Spaß machen, Ihr eigenes System zu schreiben.

0
Jørn Jensen

Es ist nicht billig, aber ich habe in meinen C++ - Tagen festgestellt, dass purify das beste Tool zum Debuggen von Lecks und anderen Speicherproblemen ist (das gleiche Tool gehört jetzt IBM, also ging der Surport bergab). Bounds Checker wurde von einigen Leuten gemocht, funktionierte aber nicht gut für die Software, die ich entwickelte.

0
Ian Ringrose