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)?
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.
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!
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.
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 .
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.
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.
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++.
Sous Linux, il existe au moins deux méthodes traditionnelles:
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.
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.
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;
}
Si j'ai besoin d'un outil, je pars généralement de ce que ma compilatrice/bibliothèque standard fournit.
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.
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.
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.
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.