web-dev-qa-db-fra.com

Comment suivre les allocations de mémoire en C++ (en particulier new/delete)

Comment puis-je suivre les allocations de mémoire en C++, en particulier celles effectuées par new/delete. Pour un objet, je peux facilement remplacer le operator new, mais je ne suis pas sûr de savoir comment remplacer globalement toutes les allocations afin qu'elles passent par ma variable new/delete. Cela ne devrait pas être un gros problème, mais je ne suis pas sûr de savoir comment cela est censé être fait (#define new MY_NEW?).

Dès que cela fonctionne, je suppose qu'il suffit de disposer d'une carte indiquant le pointeur/l'emplacement de l'allocation afin de pouvoir suivre toutes les allocations actuellement 'actives' et, à la fin de l'application, vérifier les allocations qui n'ont pas été libérés.

Eh bien, cela semble encore être quelque chose qui a sûrement été fait plusieurs fois au moins, donc toute bonne bibliothèque (de préférence portable)?

28
Anteru

Je vous recommanderais d'utiliser valgrind pour Linux. Cela capturera pas la mémoire libérée, entre autres bugs comme écrire dans de la mémoire non allouée. Une autre option est Mudflap, qui vous dit aussi de ne pas libérer de mémoire. Utilisez les options -fmudflap -lmudflap avec gcc, puis démarrez votre programme avec MUDFLAP_OPTIONS=-print-leaks ./my_program.

Voici un code très simple. Il ne convient pas au suivi sophistiqué, mais vise à vous montrer comment vous le feriez en principe si vous le mettiez en œuvre vous-même. Quelque chose comme ça (trucs omis appelant le new_handler enregistré et autres détails).

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
}

Nous devons utiliser notre propre allocateur pour notre carte, car le standard utilisera notre opérateur substitué new, ce qui entraînerait une récursion infinie.

Assurez-vous que si vous remplacez l'opérateur new, vous utilisez la carte pour enregistrer vos allocations. La suppression de la mémoire allouée par les nouvelles formes d’emplacement utilisera également cet opérateur, ce qui risque de poser problème si un code que vous ne connaissez pas a surchargé en tant que nouveau opérateur n’utilisant pas votre carte, car l’opérateur delete vous indiquera qu’il n’a utilisez std::free pour libérer la mémoire.

Notez également que, comme Pax a souligné pour sa solution, cela ne montrera que les fuites causées par du code utilisant notre propre opérateur défini new/delete. Donc, si vous voulez les utiliser, mettez leur déclaration dans un en-tête et incluez-la dans tous les fichiers à surveiller.

26

Pour être plus précis, utilisez l'outil du massif de valgrind. Contrairement à memcheck, massif n'est pas concerné par l'utilisation illégale de la mémoire mais par le suivi des allocations dans le temps. Il permet de mesurer «efficacement» l'utilisation de la mémoire de tas d'un programme. La meilleure partie est que vous n'avez pas à écrire de code. Essayer:

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

Ou si vous êtes vraiment impatient:

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

Cela vous donnera un graphique des allocations au fil du temps, et des traces en arrière où les allocations importantes se sont produites. Cet outil est optimisé pour Linux, je ne sais pas s’il existe une variante Windows. Il fonctionne fonctionne sous OS X.

Bonne chance!

24
Necro

Vous pouvez utiliser le code à l’adresse http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml avec les modifications suivantes: le code tel qu’il est donné ne fonctionne que si vous avez un seul fichier source honkin. J'ai réglé ceci pour une autre question sur SO ( ici ).

Pour commencer, don't changez stdafx.h, apportez vos modifications dans vos propres fichiers.

Créez un fichier d’en-tête séparé, mymemory.h, et insérez-y vos prototypes de fonctions (notez que cela n’a pas de body ):

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

Également dans cet en-tête, placez les autres prototypes pour AddTrack (), DumpUnfreed (), etc., et les définitions #defines, typedef et extern:

extern AllocList *allocList;

Ensuite, dans un nouveau fichier mymemory.cpp (qui contient également mymemory.h de # include), définissez la définition actuelle de allocList avec toutes les fonctions réelles (pas uniquement les prototypes) et ajoutez ce fichier à votre projet.

Ensuite, #include "mymemory.h" dans chaque fichier source dans lequel vous devez suivre la mémoire (probablement tous). Comme il n'y a pas de définitions dans le fichier d'en-tête, vous n'obtiendrez pas de doublons lors du lien et, comme les déclarations y sont présentes, vous n'obtiendrez pas non plus de références indéfinies.

Gardez à l'esprit que cela ne suivra pas les fuites de mémoire dans le code que vous ne compilez pas (par exemple, les bibliothèques tierces), mais qu'il devrait vous informer de vos propres problèmes.

9
paxdiablo

Eh bien, vous pouvez ré-implémenter les opérateurs globaux new et delete pour vous donner les fonctionnalités souhaitées, mais je vous déconseille cela, à moins que ce ne soit le seul moyen de suivre les allocations de mémoire, en raison de restrictions imposées par votre plate-forme.

Les débogueurs de mémoire sont disponibles pour la plupart des plateformes de développement courantes. Jetez un oeil sur PurifyPlus pour une solution commerciale fonctionnant sous Windows et divers Unix ou valgrind pour une solution open source fonctionnant sous Linux (et potentiellement sous d'autres systèmes d'exploitation, mais je ne l'ai jamais utilisée que sur Linux).

Si vous souhaitez remplacer les opérateurs globaux, consultez cet article .

7
Timo Geusch

Si vous développez sous Windows, l'outil gratuit DebugDiag vous aidera à trouver de la mémoire et à gérer les fuites.

Vous n'avez pas besoin d'augumenter votre programme pour que DebugDiag fonctionne.

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

Bien que ce ne soit pas le programme le plus facile ou le plus intuitif à utiliser! Assurez-vous que vous recherchez des tutoriels et des instructions sur l'utilisation de Google.

3
Ashley Davis

Pour nos projets C++ sur plate-forme Windows, j'utilise VLD, Visual Leak Detector, qui est presque trop facile à implémenter et qui permet de suivre et de signaler les fuites de mémoire lorsque votre application se ferme - le meilleur de tous est gratuit et le source est disponible. Le système peut être configuré pour générer des rapports de différentes manières (enregistreur de disque, IDE, XML, etc.) et a été très utile pour détecter les fuites dans Windows Services, qui constituent toujours un défi pour le débogage. Ainsi, si vous recherchez une solution portable, vous pouvez bien sûr consulter la source pour obtenir des conseils. J'espère que ça aide.

Pour citer le site:

C'est un moyen très efficace de diagnostiquer rapidement et de corriger les fuites de mémoire dans les applications C/C++.

http://dmoulding.googlepages.com/vld

3
Damien

Sous Linux, il existe au moins deux méthodes traditionnelles:

  • malloc () et free () (et d'autres fonctions liées à la mémoire) sont des symboles faibles, ce qui signifie que vous pouvez simplement les réimplémenter et que vos versions seront utilisées. Pour un exemple d'implémentation: voir clôture électrique.
  • Avec la variable d’environnement LD_PRELOAD, vous pouvez remplacer les symboles (faibles et forts) des bibliothèques partagées par les symboles trouvés dans les bibliothèques contenues dans la variable d’environnement LD_PRELOAD. Si vous compilez une bibliothèque partagée avec malloc (), free () et amis, vous êtes prêt. Encore une fois, la clôture électrique le démontre.

En tant que tel, vous attrapez non seulement new et delete, mais également les fonctions d'allocation de mémoire de style C. Je ne l'ai pas encore fait sur Windows, mais j'ai déjà vu des méthodes pour réécrire la façon dont les DLL sont liées là aussi (même si je me souviens qu'elles étaient un peu maladroites).

Notez cependant que, mis à part le fait que ce sont des techniques intéressantes, je vous conseillerais d'utiliser valgrind pour faire ce que vous voulez avant toute autre chose.

3
user52875

Ne répondez pas directement à votre question, mais si vous voulez vraiment obtenir une liste des objets tas qui ont fui à la fin du programme, vous pouvez simplement exécuter le programme avec valgrind .

Pour MS VS, vous pouvez jouer avec le tas CRT de débogage . Pas aussi simple que valgrind, un peu trop pour expliquer ici, mais peut faire ce que vous voulez.

1
gimpf
1
Torleif

Vérifiez ce petit code pratique, maintenant au lieu de new, utilisez NEW et suivez toutes les allocations dans le constructeur 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

Si j'ai besoin d'un outil, je pars généralement de ce que ma compilatrice/bibliothèque standard fournit.

  • Si vous utilisez glibc, vous pouvez utiliser mtrace . Il installe un point d’accès global qui enregistre chaque fonction d’allocation de mémoire glibc (malloc, realloc, memalign, free, et tout ce qui est implémenté comme neuf/supprimer)
  • Si vous utilisez Microsoft CRT, vous pouvez consulter CRT Détails du tas de débogage . Il existe des exemples pour installer la version de débogage des fonctions d’allocation de mémoire, obtenir des statistiques sur le tas, rechercher des fuites de mémoire, etc.
0
Dmitrius

Si vous développez sous Linux, l’un des meilleurs outils à cet égard (par exemple, la détection des fuites de mémoire, le suivi des allocations effectuées à certains endroits du code) est valgrind, en particulier son outil massif. Le seul inconvénient est que le programme s'exécute plus lentement (ou beaucoup plus lentement), il n'est donc utile que pour le débogage.

0
jpalecek

Vous pouvez utiliser ajouter un fichier d’en-tête (MemTracker.h) donné dans celienà votre solution pour suivre l’allocation de mémoire/désallocation en C et C++. Il indique si vous avez une fuite de mémoire et quelle ligne de code en est responsable.

0
hnl

J'ai remarqué que beaucoup d'autres réponses portaient sur les outils que vous pouvez utiliser. J'ai utilisé certains d'entre eux, et ils aident beaucoup.

Mais en tant qu’exercice de programmation, et vu que vous travaillez avec c ++, vous devrez remplacer le nouveau global et supprimer, ainsi que malloc, free et realloc. Vous penseriez que ne remplacer que new et delete suffirait, mais std :: string et les autres classes utiliseront probablement malloc et plus particulièrement realloc.

Ensuite, une fois que vous avez cela en place, vous pouvez commencer à ajouter des en-têtes pour vérifier les écrasements de mémoire, enregistrer les traces de pile par allocation, etc.

Globalement, je vous conseillerais d'utiliser l'un des outils mentionnés ici, mais il pourrait être amusant d'écrire votre propre système.

0
Jørn Jensen

Ce n’est pas bon marché, mais j’avais l'habitude de trouver dans mes journées en C++ que purifier était le meilleur outil pour déboguer les fuites et autres problèmes de mémoire. Bounds Checker a été aimé par certaines personnes, mais n'a pas bien fonctionné pour le logiciel que je développais.

0
Ian Ringrose