web-dev-qa-db-fra.com

Comment trouver une fuite de mémoire dans un projet/code C++?

Je suis un programmeur C++ sur la plate-forme Windows. J'utilise Visual Studio 2008.

Je finis habituellement dans le code avec des fuites de mémoire.

Normalement, je trouve la fuite de mémoire en inspectant le code, mais c'est fastidieux et ce n'est pas toujours une bonne approche. 

N'ayant pas les moyens d'acheter un outil de détection de fuite de mémoire payant, je voulais que vous suggériez les meilleurs moyens d'éviter les fuites de mémoire.

  1. Je veux savoir comment le programmeur peut trouver des fuites de mémoire.
  2. Existe-t-il une norme ou une procédure à suivre pour éviter toute fuite de mémoire dans le programme?
148
Chris_vr

Instructions

Choses dont vous aurez besoin

  • Maîtrise de C++
  • Compilateur C++
  • Débogueur et autres outils logiciels d'investigation

1

Comprendre les bases de l'opérateur. L'opérateur C++ "new" alloue de la mémoire heap. L'opérateur "delete" libère de la mémoire de tas. Pour chaque "nouveau", vous devez utiliser un "delete" afin de libérer la même mémoire que celle allouée:

char* str = new char [30]; // Allocate 30 bytes to house a string.

delete [] str; // Clear those 30 bytes and make str point nowhere.

2

Réallouez la mémoire uniquement si vous avez supprimé. Dans le code ci-dessous, str acquiert une nouvelle adresse avec la deuxième allocation. La première adresse est irrémédiablement perdue, de même que les 30 octets vers lesquels elle pointe. Maintenant, ils sont impossibles à libérer et vous avez une fuite de mémoire:

char* str = new char [30]; // Give str a memory address.

// delete [] str; // Remove the first comment marking in this line to correct.

str = new char [60]; /* Give str another memory address with
                                                    the first one gone forever.*/

delete [] str; // This deletes the 60 bytes, not just the first 30.

3

Regardez ces affectations de pointeurs. Chaque variable dynamique (mémoire allouée sur le tas) doit être associée à un pointeur. Lorsqu'une variable dynamique se dissocie de son ou ses pointeurs, il devient impossible de l'effacer. Encore une fois, cela entraîne une fuite de mémoire:

char* str1 = new char [30];

char* str2 = new char [40];

strcpy(str1, "Memory leak");

str2 = str1; // Bad! Now the 40 bytes are impossible to free.

delete [] str2; // This deletes the 30 bytes.

delete [] str1; // Possible access violation. What a disaster!

4

Soyez prudent avec les pointeurs locaux. Un pointeur que vous déclarez dans une fonction est alloué sur la pile, mais la variable dynamique vers laquelle il pointe est allouée sur le tas. Si vous ne le supprimez pas, il persistera après la sortie du programme de la fonction:

void Leak(int x){

char* p = new char [x];

// delete [] p; // Remove the first comment marking to correct.

}

5

Faites attention aux accolades après "supprimer". Utilisez "delete" pour libérer un seul objet. Utilisez "delete" [] avec des crochets pour libérer un tableau de tas. Ne faites pas quelque chose comme ça:

char* one = new char;

delete [] one; // Wrong

char* many = new char [30];

delete many; // Wrong!

6

Si la fuite est encore autorisée - je la cherche habituellement avec deleaker (consultez-la ici: http://deleaker.com ).

Merci!

217
John Smith

Vous pouvez utiliser certaines techniques dans votre code pour détecter une fuite de mémoire. Le moyen le plus courant et le plus simple à détecter consiste à définir une macro, par exemple DEBUG_NEW, et à l’utiliser, ainsi que de macros prédéfinies telles que __FILE__ et __LINE__ pour localiser la fuite de mémoire dans votre code. Ces macros prédéfinies vous indiquent le fichier et le numéro de ligne des fuites de mémoire.

DEBUG_NEW est juste une MACRO qui est généralement définie comme:

#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW

Ainsi, où que vous utilisiez new, il peut également garder une trace du fichier et du numéro de ligne pouvant servir à localiser une fuite de mémoire dans votre programme.

Et __FILE__, __LINE__ sont des macros prédéfinies qui correspondent respectivement au nom de fichier et au numéro de ligne où vous les utilisez!

Lisez l'article suivant qui explique très bien la technique d'utilisation de DEBUG_NEW avec d'autres macros intéressantes:

Détecteur de fuite de mémoire multiplate-forme


De Wikpedia ,

Debug_new fait référence à une technique en C++ surcharger et/ou redéfinir l'opérateur new et operator delete afin de intercepter l'allocation de mémoire et appels de désallocation, et ainsi déboguer un programme d'utilisation de la mémoire. C'est souvent implique de définir une macro nommée DEBUG_NEW et fait en sorte que le nouveau devienne quelque chose comme nouveau (_FICHIER_, _LIGNE_) enregistrer les informations de fichier/ligne sur allocation. Microsoft Visual C++ utilise cette technique dans son Microsoft Classes de fondation. Il y a quelques façons d'étendre cette méthode pour éviter en utilisant la redéfinition de la macro tout en restant capable d'afficher le fichier/ligne informations sur certaines plates-formes. Là Il existe de nombreuses limitations inhérentes à cette méthode. Cela s'applique uniquement à C++ et à ne peut pas attraper les fuites de mémoire par C fonctionne comme malloc. Cependant, il peut être très simple à utiliser et aussi très rapide, par rapport à un peu plus solutions complètes de débogueur de mémoire.

27
Nawaz

Certaines techniques de programmation bien connues vous aideront à minimiser le risque de perte de mémoire:

  • si vous devez faire votre propre allocation de mémoire dynamique, écrivez new et delete toujours deux par deux et assurez-vous que le code d'allocation/désallocation est appelé deux par deux.
  • évitez l’allocation dynamique de mémoire si vous le pouvez. Par exemple, utilisez vector<T> t chaque fois que possible au lieu de T* t = new T[size]
  • utilisez des "pointeurs intelligents", comme des pointeurs intelligents renforcés ( http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/smart_ptr.htm )
  • mon préféré: assurez-vous de bien comprendre le concept de propriété d'un pointeur et assurez-vous que partout où vous utilisez des pointeurs, vous savez quelle entité de code est le propriétaire.
  • découvrez quels constructeurs/opérateurs d'assignation sont automatiquement créés par le compilateur C++ et ce que cela signifie si vous avez une classe qui possède un pointeur (ou ce que cela signifie si vous avez une classe contenant un pointeur sur un objet, il ne not posséder).
14
Doc Brown
  1. Télécharger Outils de débogage pour Windows .
  2. Utilisez l'utilitaire gflags pour activer les traces de pile en mode utilisateur.
  3. Utilisez UMDH pour prendre plusieurs instantanés de la mémoire de votre programme. Prenez un instantané avant que la mémoire ne soit allouée et prenez un deuxième instantané après un moment où vous pensez que votre programme a perdu de la mémoire. Vous voudrez peut-être ajouter des pauses ou des invites dans votre programme pour vous permettre d'exécuter UMDH et de prendre des instantanés.
  4. Exécutez à nouveau UMDH, cette fois dans son mode qui diffère les deux instantanés. Il générera ensuite un rapport contenant les piles d'appels de fuites de mémoire présumées.
  5. Restaurez vos paramètres gflags précédents lorsque vous avez terminé.

UMDH vous donnera plus d'informations que le tas de débogage CRT car il surveille les allocations de mémoire tout au long de votre processus; il peut même vous indiquer si des composants tiers fuient.

8
Aaron Klotz
7
CantGetANick

Running "Valgrind" peut:

1) Aide pour identifier les fuites de mémoire - indique le nombre de fuites de mémoire et indique les lignes du code où la mémoire perdue a été allouée.

2) Signalez les tentatives infructueuses de libération de mémoire (par exemple, appel incorrect de "supprimer")

Instructions pour utiliser "Valgrind"

1) Obtenez valgrind ici .

1) Compilez votre code avec l'option -g

3) Dans votre Shell:

valgrind --leak-check=yes myprog arg1 arg2

Où "myprog" est votre programme compilé et "arg1", "arg2" les arguments de votre programme.

4) Le résultat est une liste d'appels à malloc/new qui n'ont pas eu d'appels ultérieurs à suppression libre.

Par exemple:

==4230==    at 0x1B977DD0: malloc (vg_replace_malloc.c:136)

==4230==    by 0x804990F: main (example.c:6)

Vous indique dans quelle ligne le malloc (qui n'a pas été libéré) a été appelé.

Comme le signalent d'autres personnes, assurez-vous que vous recevez un appel "supprimer"/"libre" à chaque nouvel appel/malloc.

6
Gal Nachmana

Si vous utilisez gcc, il y a gprof disponible.

Je voulais savoir comment le programmeur trouve une fuite de mémoire

Certains outils utilisent, certains font ce que vous faites, pourrait également grâce à l'examen du code par les pairs

Existe-t-il une norme ou une procédure à suivre pour éviter toute fuite de mémoire dans le programme?

Pour moi: chaque fois que je crée des objets alloués dynamiquement, je place toujours le code de libération après, puis le remplis entre Ce serait OK si vous êtes sûr qu'il n'y aura pas d'exceptions dans le code entre. Sinon, j'utilise try-finally (je n'utilise pas souvent le C++).

5
LeleDumbo

Recherchez dans votre code les occurrences de new et assurez-vous qu'elles se produisent toutes dans un constructeur avec une suppression correspondante dans un destructeur. Assurez-vous qu'il s'agit de la seule opération de lancement possible dans ce constructeur. Une méthode simple consiste à envelopper tous les pointeurs dans std::auto_ptr ou boost::scoped_ptr (selon que vous ayez besoin ou non de la sémantique du déplacement). Pour tout code futur, assurez-vous simplement que chaque ressource appartient à un objet qui nettoie la ressource dans son destructeur. Si vous avez besoin d'une sémantique de déplacement, vous pouvez effectuer une mise à niveau vers un compilateur prenant en charge les références de valeur r (VS2010, je crois) et créer des constructeurs de déplacement. Si vous ne voulez pas faire cela, vous pouvez utiliser une variété de techniques délicates impliquant l’utilisation consciencieuse du swap, ou essayer la bibliothèque Boost.Move.

5
Mankarse
  1. Dans Visual Studio, il existe un détecteur intégré de fuite de mémoire appelé C Runtime Library. Lorsque votre programme se ferme après le retour de la fonction principale, CRT vérifie le tas de débogage de votre application. si vous avez encore des blocs alloués sur le tas de débogage, vous avez une fuite de mémoire ..

  2. Ce forum discute de quelques moyens d'éviter les fuites de mémoire en C/C++ .. 

5
Benny Tjia

Détecteur de fuite visuel (VLD) est un système de détection de fuite de mémoire gratuit, robuste et à code source ouvert pour Visual C++.

Lorsque vous exécutez votre programme sous le débogueur Visual Studio, Visual Leak Detector génère un rapport de fuite de mémoire à la fin de votre session de débogage. Le rapport de fuite inclut la pile d'appels full indiquant comment les blocs de mémoire perdus ont été alloués. Double-cliquez sur une ligne dans la pile d'appels pour accéder à ce fichier et à cette ligne dans la fenêtre de l'éditeur.

Si vous ne disposez que de sauvegardes sur incident, vous pouvez utiliser la commande Windbg !heap -l, elle détectera les blocs de segment perdus. Mieux vaut ouvrir l’option gflags: “Créer une base de données de trace de pile en mode utilisateur”, vous verrez alors la pile d’appels d’allocation de mémoire.

4
fresky

Répondant à la deuxième partie de votre question, 

Existe-t-il une norme ou une procédure à suivre pour éviter toute fuite de mémoire dans le programme?.

Oui il y a. Et c’est l’une des principales différences entre C et C++.

En C++, vous ne devez jamais appeler new ou delete dans votre code utilisateur. RAII est une technique très utilisée, qui résout à peu près le problème de la gestion des ressources. Chaque ressource de votre programme (une ressource est tout ce qui doit être acquis, puis publié, par exemple: descripteurs de fichier, sockets réseau, connexions à la base de données, mais aussi allocations de mémoire vive et, dans certains cas, paires d’appels d’API (BeginX ( )/EndX (), LockY (), UnlockY ()) doivent être enveloppés dans une classe, où:

  • le constructeur acquiert la ressource (en appelant new si la ressource est une allocation memroy)
  • le destructeur libère la ressource,
  • la copie et l'affectation sont empêchées (en rendant le constructeur de copie et les opérateurs d'affectation privés), ou sont implémentées pour fonctionner correctement (par exemple, en clonant la ressource sous-jacente)

Cette classe est ensuite instanciée localement, sur la pile ou en tant que membre de la classe, et not en appelant new et en stockant un pointeur. 

Vous n'avez souvent pas besoin de définir ces classes vous-même. Les conteneurs de bibliothèque standard se comportent également de cette manière, de sorte que tout objet stocké dans un std::vector est libéré lorsque le vecteur est détruit. Encore une fois, ne stockez pas de pointeur dans le conteneur (ce qui nécessiterait que vous appelle new et delete), mais plutôt l’objet lui-même (qui vous donne la gestion de la mémoire pour free) . De même, les classes de pointeur intelligent peuvent être utilisées pour encapsuler facilement des objets qui doivent simplement être alloués avec new et contrôler leurs durées de vie.

Cela signifie que lorsque l'objet sort du cadre, il est automatiquement détruit et ses ressources libérées et nettoyées.

Si vous procédez ainsi de la même manière dans votre code, vous ne ferez aucune fuite de mémoire. Tout ce qui pourrait être divulgué est lié à un destructeur appelé lorsque le contrôle quitte le périmètre dans lequel l'objet a été déclaré.

3
jalf

MTuner est un outil gratuit multi-plateforme de profilage de mémoire, de détection de fuites et d’analyse, prenant en charge les compilateurs MSVC, GCC et Clang. Les fonctionnalités incluent: 

  • historique basé sur la chronologie de l'utilisation de la mémoire et des blocs de mémoire vive
  • filtrage puissant des opérations de mémoire basé sur le tas, la balise mémoire, la plage de temps, etc.
  • SDK pour instrumentation manuelle avec code source complet
  • prise en charge de l'intégration continue via l'utilisation de la ligne de commande
  • navigation entre arbres
  • beaucoup plus. 

Les utilisateurs peuvent profiler n’importe quel logiciel ciblant les plates-formes avec les compilateurs GCC ou Clang cross MTuner est livré avec un support intégré pour les plates-formes Windows, PlayStation 4 et PlayStation 3.

3
mtosic

Sous Windows, vous pouvez utiliser CRT debug heap .

Existe-t-il une norme ou une procédure à suivre pour éviter toute fuite de mémoire dans le programme?.

Ouais, n'utilisez pas la gestion manuelle de la mémoire (si vous appelez delete ou delete[] manuellement, vous vous trompez). Utilisez RAII et les pointeurs intelligents, limitez les allocations de tas au minimum absolu (la plupart du temps, des variables automatiques suffisent).

3
Cat Plus Plus

Vous pouvez utiliser l'outil Valgrind pour détecter les fuites de mémoire.

De plus, pour rechercher la fuite dans une fonction particulière, utilisez exit (0) à la fin de la fonction, puis exécutez-la avec Valgrind.

`$` valgrind ./your_CPP_program 
2
Divyanshu

AddressSanitizer (ASan) est un détecteur d'erreur de mémoire rapide. Il trouve des bogues de débordement du tampon} après utilisation et {tas, pile, global} dans les programmes C/C++. Il trouve:

  • Utiliser après libre (déréférence du pointeur en suspens) 
  • Dépassement de mémoire tampon
  • Dépassement de tampon de pile 
  • Débordement de tampon global 
  • Utiliser après le retour
  • Bugs d'ordre d'initialisation

Cet outil est très rapide. Le ralentissement moyen du programme instrumenté est d'environ 2x.

2
Beginner

En plus des outils et des méthodes fournis dans les autres réponses, des outils d’analyse de code statique peuvent être utilisés pour détecter les fuites de mémoire (et d’autres problèmes également) .. Cppcheck est un outil robuste et gratuit. Mais il y a beaucoup d'autres outils disponibles. Wikipedia a une liste d'outils d'analyse de code statique.

0
orbitcowboy

Ni "nouveau" ni "supprimer" ne doivent jamais être utilisés dans le code de l'application. Au lieu de cela, créez un nouveau type qui utilise l'idiome manager/worker, dans lequel la classe manager alloue et libère de la mémoire et transmet toutes les autres opérations à l'objet worker.

Malheureusement, cela représente plus de travail que prévu, car C++ ne surcharge pas "opérateur". C'est encore plus de travail en présence de polymorphisme.

Mais cela en vaut la peine, car vous n'avez alors plus à vous soucier des fuites de mémoire, ce qui signifie que vous n'avez même pas à les rechercher.

0
s. heller