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

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

Je finis généralement dans le code avec des memory leaks.

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

Comme je ne peux pas me permettre un outil de détection des memory leaks, je voulais que vous suggériez les meilleurs moyens d’éviter les memory leaks.

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

Instructions

Les choses dont vous aurez besoin

  • Maîsortingse du 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 une mémoire de tas. L’opérateur “delete” libère la mémoire du tas. Pour chaque “nouveau”, vous devez utiliser un “supprimer” afin de libérer la même mémoire que vous avez allouée:

char* str = new char [30]; // Allocate 30 bytes to house a ssortingng. delete [] str; // Clear those 30 bytes and make str point nowhere. 

2

Réaffectez la mémoire uniquement si vous avez supprimé. Dans le code ci-dessous, str acquiert une nouvelle adresse avec la seconde allocation. La première adresse est irrémédiablement perdue, tout comme 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 assignations de pointeur. Chaque variable dynamic (mémoire allouée sur le tas) doit être associée à un pointeur. Lorsqu’une variable dynamic est dissociée de son ou de 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 stack, mais la variable dynamic 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” par lui-même pour libérer un seul object. Utilisez “delete” [] avec des crochets pour libérer un tableau de tas. Ne fais pas quelque chose comme ça:

 char* one = new char; delete [] one; // Wrong char* many = new char [30]; delete many; // Wrong! 

6

Si la fuite a encore permis – je cherche généralement avec deleaker (consultez-le ici: http://deleaker.com ).

Merci!

Vous pouvez utiliser certaines techniques dans votre code pour détecter les memory leaks. La méthode la plus courante et la plus simple pour détecter est de définir une macro, par exemple DEBUG_NEW, et de l’utiliser, avec des macros prédéfinies comme __FILE__ et __LINE__ pour localiser la fuite de mémoire dans votre code. Ces macros prédéfinies vous indiquent le nombre de fichiers et de lignes des memory leaks.

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

 #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 qui pourraient être utilisés pour 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 la technique d’utilisation de DEBUG_NEW avec d’autres macros intéressantes, très bien:

Un détecteur de fuite de mémoire multi-plateforme


De Wikpedia ,

Debug_new fait référence à une technique en C ++ pour surcharger et / ou redéfinir les opérateurs new et operator delete afin d’intercepter les appels d’allocation de mémoire et de désallocation, et donc déboguer un programme pour l’utilisation de la mémoire. Cela implique souvent de définir une macro nommée DEBUG_NEW, et de faire en sorte que new devienne quelque chose de nouveau (_ FILE _, _ LINE _) pour enregistrer les informations de fichier / ligne sur l’allocation. Microsoft Visual C ++ utilise cette technique dans ses classes Microsoft Foundation. Il existe des moyens d’étendre cette méthode pour éviter la redéfinition des macros tout en permettant d’afficher les informations de fichier / ligne sur certaines plates-formes. Il existe de nombreuses limitations inhérentes à cette méthode. Cela s’applique uniquement à C ++, et ne peut pas intercepter les memory leaks par les fonctions C comme malloc. Cependant, il peut être très simple à utiliser et très rapide, comparé à des solutions de débogage de mémoire plus complètes.

Certaines techniques de programmation bien connues vous aideront à minimiser le risque de memory leaks:

  • Si vous devez effectuer votre propre allocation de mémoire dynamic, écrivez-en toutes les new et delete toujours, et assurez-vous que le code d’allocation / désallocation est appelé par paire
  • évitez d’allouer de la mémoire dynamic si vous le pouvez. Par exemple, utilisez le vector t si possible au lieu de T* t = new T[size]
  • utiliser des “pointeurs intelligents” comme des pointeurs intelligents ( http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/smart_ptr.htm )
  • mon favori personnel: assurez-vous d’avoir compris 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
  • apprenez quels constructeurs / opérateurs d’affectation 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 object qu’il ne possède pas ).
  1. Téléchargez les outils de débogage pour Windows .
  2. Utilisez l’utilitaire gflags pour activer les traces de stack 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 effectuez un deuxième instantané après un point auquel vous pensez que votre programme a une fuite de mémoire. Vous voudrez peut-être append des pauses ou des invites dans votre programme pour vous donner une chance d’exécuter UMDH et de prendre les instantanés.
  4. Exécutez à nouveau UMDH , cette fois dans son mode qui fait un diff entre les deux instantanés. Il générera ensuite un rapport contenant les stacks d’appels de memory leaks suspectées.
  5. Restaurez vos parameters 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 sur l’ensemble de votre processus. il peut même vous dire si des composants tiers fuient.

Si vous utilisez gcc, gprof est disponible.

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

Certains utilisent des outils, d’autres font ce que vous faites, et peuvent également être révisés par des 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 objects dynamicment alloués, je mets toujours le code de libération après, puis remplis le code entre. Ce serait correct 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 fréquemment C ++).

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

  2. Ce forum aborde quelques manières d’éviter les memory leaks en C / C ++.

Recherchez les occurrences de new votre code et assurez-vous qu’elles se produisent toutes dans un constructeur avec une suppression correspondante dans un destructeur. Assurez-vous que c’est la seule opération de lancement possible dans ce constructeur. Une manière simple de procéder consiste à envelopper tous les pointeurs dans std::auto_ptr ou boost::scoped_ptr (selon que vous avez besoin ou non de sémantique de déplacement). Pour tout futur code, assurez-vous simplement que chaque ressource appartient à un object qui nettoie la ressource dans son destructeur. Si vous avez besoin de déplacer la sémantique, vous pouvez effectuer une mise à niveau vers un compilateur qui prend en charge les références de valeur r (VS2010, à mon avis) 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 une utilisation consciencieuse du swap ou essayer la bibliothèque Boost.Move.

Lancer “Valgrind” peut:

1) Aidez à identifier les memory leaks – montrez combien de memory leaks vous avez, et indiquez les lignes dans le code où la fuite de mémoire a été allouée.

2) Indiquez des tentatives erronées pour libérer de la mémoire (par exemple, appel incorrect de “delete”)

Instructions pour l’utilisation de “Valgrind”

1) Obtenez valgrind ici .

1) Comstackz votre code avec l’option -g

3) Dans votre shell, exécutez:

 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 reçu d’appels ultérieurs de 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 souligné par d’autres, assurez-vous que pour chaque appel “new” / “malloc”, vous avez un appel “delete” / “free” ultérieur.

Sous Windows, vous pouvez utiliser le segment de débogage CRT .

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

Oui, n’utilisez pas la gestion de mémoire manuelle (si vous appelez jamais delete ou delete[] manuellement, alors vous le faites mal). Utilisez RAII et les pointeurs intelligents, limitez les allocations de tas au minimum absolu (la plupart du temps, les variables automatiques suffisent).

Répondre à 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 couramment 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 libéré: descripteurs de fichier, sockets réseau, connexions à la firebase database, mais aussi allocations de mémoire et, dans certains cas, paires d’appels 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 privés le constructeur de la copie et les opérateurs d’affectation) 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 stack ou en tant que membre de classe, et non 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 object stocké dans un std::vector est libéré lorsque le vecteur est détruit. Donc, encore une fois, ne stockez pas un pointeur dans le conteneur (ce qui vous obligerait à appeler new et delete ), mais plutôt l’object lui-même (qui vous donne la gestion de la mémoire gratuitement ). De même, les classes de pointeurs intelligents peuvent être utilisées pour encapsuler facilement des objects qui doivent simplement être alloués avec new et contrôler leur durée de vie.

Cela signifie que lorsque l’object est hors de scope, il est automatiquement détruit et sa ressource libérée et nettoyée.

Si vous le faites de manière cohérente dans tout votre code, vous n’aurez tout simplement aucune fuite de mémoire. Tout ce qui pourrait être divulgué est lié à un destructeur garanti à être appelé lorsque le contrôle quitte la scope dans laquelle l’object a été déclaré.

Visual Leak Detector (VLD) est un système de détection de memory leaks open source gratuit et robuste 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 comprend la stack d’appels complète indiquant comment les blocs de mémoire qui ont fui ont été alloués. Double-cliquez sur une ligne de la stack d’appels pour accéder à ce fichier et à cette ligne dans la fenêtre de l’éditeur.

Si vous ne disposez que de vidages sur incident, vous pouvez utiliser la commande Windbg !heap -l , elle détectera les blocs de stack qui ont fui. Il est préférable d’ouvrir l’option gflags: «Créer une firebase database de trace de la stack en mode utilisateur», vous verrez alors la stack d’appels d’allocation de mémoire.

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

  • historique chronologique de l’utilisation de la mémoire et des blocs de mémoire en direct
  • filtrage puissant des opérations de mémoire basé sur le tas, la balise mémoire, la plage de temps, etc.
  • SDK pour l’instrumentation manuelle avec code source complet
  • Prise en charge de l’continuous integration via l’utilisation de la ligne de commande
  • navigation dans les arbres de stack d’appels et dans les cartes d’arborescence
  • beaucoup plus.

Les utilisateurs peuvent profiler tout logiciel ciblant des plates-formes avec des compilateurs GCC ou Clang. MTuner comprend un support intégré pour les plates-formes Windows, PlayStation 4 et PlayStation 3.

AddressSanitizer (ASan) est un détecteur d’erreur de mémoire rapide. Il trouve des bogues de débordement avec tampon libre, {heap, stack, global} dans les programmes C / C ++. Il trouve:

  • Utiliser après libre (déréférencement de pointeur pendant)
  • Débordement de la mémoire tampon
  • Dépassement du tampon de stack
  • Débordement global de la mémoire tampon
  • Utiliser après retour
  • Bugs d’ordre d’initialisation

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

Vous pouvez utiliser l’outil Valgrind pour détecter les memory leaks.

En outre, pour trouver la fuite dans une fonction particulière, utilisez exit (0) à la fin de la fonction, puis exécutez-le avec Valgrind.

 `$` valgrind ./your_CPP_program 

En plus des outils et des méthodes fournis dans les autres réponses, des outils d’parsing de code statique peuvent être utilisés pour détecter les memory leaks (ainsi que d’autres problèmes). Un outil gratuit et robuste est Cppcheck. Mais il y a beaucoup d’autres outils disponibles. Wikipedia a une liste d’outils d’parsing de code statique.

Cela pourrait aider quelqu’un qui veut juste utiliser Visual Studio pour la détection des fuites. Les “Outils de diagnostic” sont livrés avec VS 2015 et les versions ci-dessus ont été considérablement améliorées. Aussi essayé outil appelé “Deleaker”, mais Visual Studio Tool est tout aussi bon. Regarder la vidéo suivante m’a aidé à le démarrer.

https://www.youtube.com/watch?v=HUZW8m_3XvE

Ni “new” ni “delete” ne doivent jamais être utilisés dans le code de l’application. Créez plutôt un nouveau type qui utilise l’idiome manager / worker, dans lequel la classe du gestionnaire alloue et libère de la mémoire et transfère toutes les autres opérations à l’object worker.

Malheureusement, cela représente plus de travail que prévu car C ++ ne contient pas de “opérateur”. C’est encore plus en présence de polymorphism.

Mais cela en vaut la peine, car vous n’avez plus à vous soucier des memory leaks, ce qui signifie que vous n’avez même pas à les chercher.