Consignes générales pour éviter les fuites de mémoire en C ++ [fermé]

130

Quels sont quelques conseils généraux pour m'assurer de ne pas fuir de mémoire dans les programmes C ++? Comment déterminer qui doit libérer de la mémoire allouée dynamiquement?

dulipishi
la source
26
Cela me semble assez constructif.
Shoerob
11
C'est constructif. Et les réponses sont étayées par des faits, des expertises, des références, etc. Et voyez le nombre de votes positifs / réponses .. !!
Samitha Chathuranga

Réponses:

40

Au lieu de gérer la mémoire manuellement, essayez d'utiliser des pointeurs intelligents le cas échéant.
Jetez un œil à Boost lib , TR1 et les pointeurs intelligents .
De plus, les pointeurs intelligents font désormais partie de la norme C ++ appelée C ++ 11 .

Andri Möll
la source
1
Pour compiler en utilisant g ++, il faut ajouter le paramètre: -std = c ++ 0x
Paweł Szczur
ou vous pouvez compiler avec g ++ en utilisant la valeur de l'indicateur -std = c ++ 11
Prabhash Rathore
200

J'approuve entièrement tous les conseils sur RAII et les pointeurs intelligents, mais j'aimerais également ajouter un conseil de niveau légèrement supérieur: la mémoire la plus facile à gérer est la mémoire que vous n'avez jamais allouée. Contrairement aux langages comme C # et Java, où à peu près tout est une référence, en C ++, vous devez mettre des objets sur la pile chaque fois que vous le pouvez. Comme je l'ai vu plusieurs personnes (y compris le Dr Stroustrup) le faire remarquer, la raison principale pour laquelle le garbage collection n'a jamais été populaire en C ++ est que C ++ bien écrit ne produit pas beaucoup de déchets en premier lieu.

N'écris pas

Object* x = new Object;

ou même

shared_ptr<Object> x(new Object);

quand tu peux juste écrire

Object x;
Ross Smith
la source
34
J'aimerais pouvoir lui donner un +10. C'est le plus gros problème que je vois avec la plupart des programmeurs C ++ aujourd'hui, et je suppose que c'est parce qu'ils ont appris Java avant C ++.
Kristopher Johnson
Point très intéressant - je m'étais demandé pourquoi j'avais des problèmes de gestion de mémoire C ++ beaucoup moins souvent que dans d'autres langages, mais maintenant je vois pourquoi: cela permet en fait à des choses d'aller sur la pile comme dans vanilla C.
ArtOfWarfare
Alors, que faites-vous si vous écrivez Object x; et ensuite vouloir jeter x? disons que x a été créé dans la méthode principale.
Yamcha
3
@ user1316459 C ++ vous permet également de créer des portées à la volée. Tout ce que vous avez à faire est d'envelopper la durée de vie de x entre accolades comme ceci: {Object x; x.DoSomething; }. Après le '}' final, le destructeur de x sera appelé pour libérer toutes les ressources qu'il contient. Si x, lui-même, est la mémoire à allouer sur le tas, je suggère de l'envelopper dans un unique_ptr afin qu'il soit nettoyé facilement et correctement.
David Peterson
1
Robert: oui. Ross n'a pas dit "N'écrivez jamais [le code contenant un nouveau]", il a dit "N'écrivez pas [cela] quand vous pouvez simplement [le mettre sur la pile]". Les objets volumineux sur le tas continueront d'être le bon appel dans la plupart des situations, en particulier pour le code à haute performance.
codetaku
104

Utiliser RAII

  • Oubliez la collecte des ordures (utilisez plutôt RAII). Notez que même le garbage collector peut également fuir (si vous oubliez de "null" certaines références en Java / C #), et que le garbage collector ne vous aidera pas à disposer des ressources (si vous avez un objet qui a acquis un handle pour un fichier, le fichier ne sera pas libéré automatiquement lorsque l'objet sortira de la portée si vous ne le faites pas manuellement en Java, ou utilisez le modèle «disposer» en C #).
  • Oubliez la règle «un retour par fonction» . C'est un bon conseil en C pour éviter les fuites, mais il est obsolète en C ++ en raison de son utilisation d'exceptions (utilisez plutôt RAII).
  • Et bien que le "Sandwich Pattern" soit un bon conseil en C, il est obsolète en C ++ en raison de son utilisation d'exceptions (utilisez plutôt RAII).

Ce message semble être répétitif, mais en C ++, le modèle le plus basique à connaître est RAII .

Apprenez à utiliser des pointeurs intelligents, à la fois de boost, TR1 ou même du modeste (mais souvent assez efficace) auto_ptr (mais vous devez connaître ses limites).

RAII est à la fois la base de la sécurité des exceptions et de l'élimination des ressources en C ++, et aucun autre modèle (sandwich, etc.) ne vous donnera les deux (et la plupart du temps, il ne vous en donnera aucun).

Voir ci-dessous une comparaison du code RAII et non RAII:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

À propos de RAII

Pour résumer (après le commentaire d' Ogre Psalm33 ), RAII repose sur trois concepts:

  • Une fois l'objet construit, ça marche! Acquérez des ressources dans le constructeur.
  • La destruction d'objets suffit! Libérez des ressources dans le destructeur.
  • Tout est question de lunettes! Les objets scoped (voir l'exemple doRAIIStatic ci-dessus) seront construits à leur déclaration, et seront détruits au moment où l'exécution quitte la portée, peu importe comment la sortie (retour, rupture, exception, etc.).

Cela signifie que dans un code C ++ correct, la plupart des objets ne seront pas construits avec newet seront déclarés sur la pile à la place. Et pour ceux construits en utilisant new, tout sera en quelque sorte limité (par exemple attaché à un pointeur intelligent).

En tant que développeur, c'est en effet très puissant car vous n'aurez pas besoin de vous soucier de la gestion manuelle des ressources (comme cela se fait en C, ou pour certains objets en Java qui utilisent intensivement try/ finallypour ce cas) ...

Modifier (12/02/2012)

"les objets à portée ... seront détruits ... peu importe la sortie" ce n'est pas tout à fait vrai. il existe des moyens de tromper RAII. toute saveur de terminate () contournera le nettoyage. exit (EXIT_SUCCESS) est un oxymore à cet égard.

- wilhelmtell

wilhelmtell a tout à fait raison à ce sujet: il existe des moyens exceptionnels de tromper RAII, qui mènent tous à l'arrêt brutal du processus.

Ce sont des moyens exceptionnels car le code C ++ n'est pas jonché de terminate, exit, etc., ou dans le cas d'exceptions, nous voulons qu'une exception non gérée plante le processus et vider son image mémoire telle quelle, et non après le nettoyage.

Mais nous devons toujours être au courant de ces cas car, s'ils se produisent rarement, ils peuvent toujours se produire.

(qui appelle terminateou exiten code C ++ occasionnel? ... Je me souviens avoir dû faire face à ce problème en jouant avec GLUT : Cette bibliothèque est très orientée C, allant jusqu'à la concevoir activement pour rendre les choses difficiles pour les développeurs C ++ comme ne pas s'en soucier sur les données allouées à la pile , ou avoir des décisions "intéressantes" pour ne jamais revenir de leur boucle principale ... je ne commenterai pas à ce sujet) .

paercebal
la source
La classe T ne doit-elle pas utiliser RAII pour être sûre que doRAIIStatic () ne fuit pas de mémoire? Par exemple T p (); p.doSandwich (); Cependant, je ne sais pas vraiment grand-chose à ce sujet.
Daniel O
@Ogre Psalm33: Merci pour le commentaire. Bien sûr, vous avez raison. J'ai ajouté les deux liens vers la page Wikipédia RAII et un petit résumé de ce qu'est RAII.
paercebal
1
@Shiftbit: Trois façons, par ordre de préférence: _ _ _ 1. Placez un objet réel dans le conteneur STL. _ _ _ 2. Placez les pointeurs intelligents (shared_ptr) des objets dans le conteneur STL. _ _ _ 3. Placez des pointeurs bruts dans le conteneur STL, mais enveloppez le conteneur pour contrôler tout accès aux données. L'encapsuleur s'assurera que le destructeur libérera les objets alloués et les accesseurs d'encapsuleur s'assureront que rien n'est cassé lors de l'accès / de la modification du conteneur.
paercebal
1
@Robert: En C ++ 03, vous utiliseriez doRAIIDynamic dans une fonction qui doit donner la propriété à une fonction enfant ou parent (ou à une portée globale). Ou lorsque vous recevez une interface vers un objet polymorphe via une fabrique (renvoyant un pointeur intelligent, s'il est correctement écrit). En C ++ 11, c'est moins le cas car vous pouvez rendre votre objet mobile, donc donner la propriété d'un objet déclaré sur la pile est plus facile ...
paercebal
2
@Robert: ... Notez que déclarer un objet sur la pile ne signifie pas que l'objet n'utilise pas le tas en interne (notez la double négation ... :-) ...). Par exemple, std :: string implémenté avec Small String Optimization aura un tampon "sur la pile de la classe" pour les petites chaînes (~ 15 caractères), et utilisera un pointeur vers une mémoire dans le tas pour les chaînes plus grandes ... Mais de l'extérieur, std :: string est toujours un type de valeur que vous déclarez (généralement) sur la pile et que vous utilisez comme vous utiliseriez un entier (par opposition à: comme vous utiliseriez une interface pour une classe polymorphe).
paercebal
25

Vous voudrez regarder des pointeurs intelligents, tels que les pointeurs intelligents de Boost .

Au lieu de

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost :: shared_ptr supprimera automatiquement une fois que le nombre de références est nul:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

Notez ma dernière remarque, "lorsque le nombre de références est égal à zéro, ce qui est la partie la plus cool. Donc, si vous avez plusieurs utilisateurs de votre objet, vous n'aurez pas à savoir si l'objet est toujours utilisé. Une fois que personne ne fait référence à votre pointeur partagé, il est détruit.

Ce n'est cependant pas une panacée. Bien que vous puissiez accéder au pointeur de base, vous ne voudriez pas le transmettre à une API tierce à moins d'être sûr de ce qu'il faisait. Souvent, votre "publication" de trucs sur un autre thread pour que le travail soit fait APRÈS que la création de la portée soit terminée. Ceci est courant avec PostThreadMessage dans Win32:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

Comme toujours, utilisez votre capuchon de réflexion avec n'importe quel outil ...

Doug T.
la source
12

Renseignez-vous sur RAII et assurez-vous de le comprendre.

Hank
la source
11

La plupart des fuites de mémoire sont le résultat d'un manque de clarté sur la propriété et la durée de vie des objets.

La première chose à faire est d'allouer sur la pile chaque fois que vous le pouvez. Cela concerne la plupart des cas où vous devez allouer un seul objet dans un but précis.

Si vous avez besoin de «nouveau» un objet, la plupart du temps, il aura un seul propriétaire évident pour le reste de sa vie. Pour cette situation, j'ai tendance à utiliser un tas de modèles de collections conçus pour «posséder» des objets qui y sont stockés par pointeur. Ils sont implémentés avec le vecteur STL et les conteneurs cartographiques mais présentent quelques différences:

  • Ces collections ne peuvent pas être copiées ni attribuées. (une fois qu'ils contiennent des objets.)
  • Des pointeurs vers des objets y sont insérés.
  • Lorsque la collection est supprimée, le destructeur est d'abord appelé sur tous les objets de la collection. (J'ai une autre version où il affirme s'il est détruit et non vide.)
  • Comme ils stockent des pointeurs, vous pouvez également stocker des objets hérités dans ces conteneurs.

Mon point de vue avec STL est qu'il est tellement concentré sur les objets Value alors que dans la plupart des applications, les objets sont des entités uniques qui n'ont pas de sémantique de copie significative requise pour une utilisation dans ces conteneurs.

Jeroen Dirks
la source
10

Bah, vous les jeunes enfants et vos nouveaux éboueurs ...

Règles très strictes sur la «propriété» - quel objet ou partie du logiciel a le droit de supprimer l'objet. Des commentaires clairs et des noms de variables sages pour rendre évident si un pointeur "possède" ou est "regarde, ne touche pas". Pour aider à décider qui possède quoi, suivez autant que possible le modèle «sandwich» dans chaque sous-programme ou méthode.

create a thing
use that thing
destroy that thing

Parfois, il est nécessaire de créer et de détruire dans des endroits très différents; je pense difficile d'éviter cela.

Dans tout programme nécessitant des structures de données complexes, je crée un arbre strict et clair d'objets contenant d'autres objets - en utilisant des pointeurs "propriétaire". Cet arbre modélise la hiérarchie de base des concepts de domaine d'application. Exemple une scène 3D possède des objets, des lumières, des textures. À la fin du rendu, lorsque le programme se ferme, il existe un moyen clair de tout détruire.

De nombreux autres pointeurs sont définis selon les besoins chaque fois qu'une entité a besoin d'accéder à une autre, pour balayer des arays ou autre; ce sont les "juste à la recherche". Pour l'exemple de la scène 3D - un objet utilise une texture mais ne la possède pas; d'autres objets peuvent utiliser la même texture. La destruction d'un objet ne pas invoquer la destruction de toutes les textures.

Oui, cela prend du temps mais c'est ce que je fais. J'ai rarement des fuites de mémoire ou d'autres problèmes. Mais ensuite, je travaille dans l'arène limitée des logiciels scientifiques, d'acquisition de données et de graphiques haute performance. Je ne traite pas souvent des transactions comme dans la banque et le commerce électronique, des interfaces graphiques basées sur les événements ou un chaos asynchrone en réseau élevé. Peut-être que les nouvelles méthodes ont un avantage là-bas!

DarenW
la source
Je suis totalement d'accord. En travaillant dans un environnement intégré, vous n'aurez peut-être pas non plus le luxe de disposer de bibliothèques tierces.
simon
6
Je ne suis pas d'accord. dans la partie de "utiliser cette chose", si un retour ou une exception est lancé, alors vous manquerez la désallocation. Quant aux performances, le std :: auto_ptr ne vous coûterait rien. Non pas que je ne code jamais de la même manière que vous. C'est juste qu'il y a une différence entre un code sécurisé à 100% et 99%. :-)
paercebal
8

Excellente question!

si vous utilisez c ++ et que vous développez une application boudeur CPU-et-mémoire en temps réel (comme des jeux), vous devez écrire votre propre gestionnaire de mémoire.

Je pense que le mieux que vous puissiez faire est de fusionner des œuvres intéressantes de divers auteurs, je peux vous donner un indice:

  • L'allocateur de taille fixe est fortement discuté, partout sur le net

  • Small Object Allocation a été introduit par Alexandrescu en 2001 dans son livre parfait "Modern c ++ design"

  • Une grande avancée (avec le code source distribué) peut être trouvée dans un article étonnant de Game Programming Gem 7 (2008) intitulé "High Performance Heap allocator" écrit par Dimitar Lazarov

  • Une grande liste de ressources peut être trouvée dans cet article

Ne commencez pas par vous-même à écrire un allocateur inutile ... DOCUMENTER VOUS-MÊME d'abord.

ugasoft
la source
5

Une technique qui est devenue populaire avec la gestion de la mémoire en C ++ est RAII . Fondamentalement, vous utilisez des constructeurs / destructeurs pour gérer l'allocation des ressources. Bien sûr, il y a d'autres détails désagréables dans C ++ en raison de la sécurité des exceptions, mais l'idée de base est assez simple.

Le problème se résume généralement à un problème de propriété. Je recommande vivement de lire la série Effective C ++ de Scott Meyers et Modern C ++ Design par Andrei Alexandrescu.

Jason Dagit
la source
5

Il y a déjà beaucoup de choses sur la façon de ne pas fuir, mais si vous avez besoin d'un outil pour vous aider à suivre les fuites, jetez un œil à:

fabiopedrosa
la source
BoundsChecker est 404ing.
TankorSmash
4

Des pointeurs intelligents pour les utilisateurs partout où vous le pouvez! Des classes entières de fuites de mémoire disparaissent.

DougN
la source
4

Partagez et connaissez les règles de propriété de la mémoire dans votre projet. L'utilisation des règles COM assure la meilleure cohérence (les paramètres [in] appartiennent à l'appelant, l'appelé doit copier; les paramètres [out] appartiennent à l'appelant, l'appelé doit faire une copie s'il garde une référence; etc.)

Seth Morris
la source
4

valgrind est également un bon outil pour vérifier les fuites de mémoire de vos programmes au moment de l'exécution.

Il est disponible sur la plupart des versions de Linux (y compris Android) et sur Darwin.

Si vous utilisez pour écrire des tests unitaires pour vos programmes, vous devriez prendre l'habitude d'exécuter systématiquement valgrind sur les tests. Cela évitera potentiellement de nombreuses fuites de mémoire à un stade précoce. Il est également généralement plus facile de les identifier dans des tests simples que dans un logiciel complet.

Bien entendu, ces conseils restent valables pour tout autre outil de vérification de la mémoire.

kriss
la source
3

De plus, n'utilisez pas de mémoire allouée manuellement s'il existe une classe de bibliothèque std (par exemple, vector). Assurez-vous que si vous enfreignez cette règle, vous disposez d'un destructeur virtuel.

Joseph
la source
2

Si vous ne pouvez pas / ne pas utiliser un pointeur intelligent pour quelque chose (bien que cela devrait être un énorme drapeau rouge), tapez votre code avec:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

C'est évident, mais assurez-vous de le saisir avant de taper du code dans la portée

Seth Morris
la source
2

Une source fréquente de ces bogues est lorsque vous avez une méthode qui accepte une référence ou un pointeur vers un objet mais laisse la propriété floue. Les conventions de style et de commentaire peuvent rendre cela moins probable.

Soit le cas où la fonction prend possession de l'objet soit le cas particulier. Dans toutes les situations où cela se produit, assurez-vous d'écrire un commentaire à côté de la fonction dans le fichier d'en-tête indiquant cela. Vous devez vous assurer que dans la plupart des cas, le module ou la classe qui alloue un objet est également responsable de sa désallocation.

L'utilisation de const peut aider beaucoup dans certains cas. Si une fonction ne modifie pas un objet et ne stocke pas de référence à celui-ci qui persiste après son retour, acceptez une référence const. En lisant le code de l'appelant, il sera évident que votre fonction n'a pas accepté la propriété de l'objet. Vous auriez pu avoir la même fonction accepter un pointeur non-const, et l'appelant peut ou non avoir supposé que l'appelé acceptait la propriété, mais avec une référence const, il n'y a pas de question.

N'utilisez pas de références non const dans les listes d'arguments. Il est très difficile de savoir lors de la lecture du code de l'appelant que l'appelé peut avoir conservé une référence au paramètre.

Je ne suis pas d'accord avec les commentaires recommandant des pointeurs de référence. Cela fonctionne généralement bien, mais lorsque vous avez un bogue et que cela ne fonctionne pas, surtout si votre destructeur fait quelque chose de non trivial, comme dans un programme multithread. Essayez certainement d'ajuster votre conception pour ne pas avoir besoin de comptage de références si ce n'est pas trop difficile.

Jonathan
la source
2

Conseils par ordre d'importance:

-Tip # 1 N'oubliez jamais de déclarer vos destructeurs "virtuels".

-Tip # 2 Utilisez RAII

-Tip # 3 Utilisez les smartpointers de Boost

-Conseil n ° 4 N'écrivez pas vos propres Smartpointers bogués, utilisez boost (sur un projet sur lequel je suis en ce moment, je ne peux pas utiliser boost, et j'ai souffert d'avoir à déboguer mes propres pointeurs intelligents, je ne prendrais certainement pas la même route encore, mais encore une fois pour le moment je ne peux pas ajouter de boost à nos dépendances)

-Tip # 5 Si c'est quelque chose de critique occasionnel / non-performance (comme dans les jeux avec des milliers d'objets), regardez le conteneur de pointeur de boost de Thorsten Ottosen

-Tip # 6 Trouvez un en-tête de détection de fuite pour votre plate-forme de choix tel que l'en-tête "vld" de Visual Leak Detection

Robert Gould
la source
Il me manque peut-être une astuce, mais comment les mots «jeu» et «non critique pour les performances» peuvent-ils être dans la même phrase?
Adam Naylor
Les jeux sont un exemple du scénario critique bien sûr. Peut-être n'a pas pu être clair là
Robert Gould
Le conseil n ° 1 ne doit être appliqué que si la classe a au moins une méthode virtuelle. Je n'imposerais jamais un destructeur virtuel inutile à une classe qui n'est pas destinée à servir de classe de base dans un arbre d'héritage polymorphe.
avant
1

Si vous le pouvez, utilisez boost shared_ptr et auto_ptr standard C ++. Ceux-ci véhiculent une sémantique de propriété.

Lorsque vous renvoyez un auto_ptr, vous dites à l'appelant que vous lui donnez la propriété de la mémoire.

Lorsque vous renvoyez un shared_ptr, vous dites à l'appelant que vous y avez une référence et qu'il en fait partie, mais ce n'est pas uniquement sa responsabilité.

Cette sémantique s'applique également aux paramètres. Si l'appelant vous passe un auto_ptr, il vous donne la propriété.

Justin Rudd
la source
1

D'autres ont mentionné des moyens d'éviter les fuites de mémoire en premier lieu (comme les pointeurs intelligents). Mais un outil de profilage et d'analyse de la mémoire est souvent le seul moyen de dépister les problèmes de mémoire une fois que vous les avez.

Valgrind Memcheck est un excellent logiciel gratuit.

eli
la source
1

Pour MSVC uniquement, ajoutez ce qui suit en haut de chaque fichier .cpp:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

Ensuite, lors du débogage avec VS2003 ou supérieur, vous serez informé de toute fuite lorsque votre programme se termine (il suit nouveau / suppression). C'est basique, mais cela m'a aidé dans le passé.

Rob
la source
1

valgrind (disponible uniquement pour les plates-formes * nix) est un très bon vérificateur de mémoire

Ronny Brendel
la source
1

Si vous comptez gérer votre mémoire manuellement, vous avez deux cas:

  1. J'ai créé l'objet (peut-être indirectement, en appelant une fonction qui alloue un nouvel objet), je l'utilise (ou une fonction que j'appelle l'utilise), puis je le libère.
  2. Quelqu'un m'a donné la référence, donc je ne devrais pas la libérer.

Si vous devez enfreindre l'une de ces règles, veuillez la documenter.

Tout est question de propriété du pointeur.

Null303
la source
1
  • Essayez d'éviter d'allouer des objets de manière dynamique. Tant que les classes ont des constructeurs et des destructeurs appropriés, utilisez une variable du type classe, pas un pointeur vers elle, et vous évitez l'allocation et la désallocation dynamiques car le compilateur le fera pour vous.
    En fait, c'est aussi le mécanisme utilisé par les "pointeurs intelligents" et appelé RAII par certains des autres auteurs ;-).
  • Lorsque vous passez des objets à d'autres fonctions, préférez les paramètres de référence aux pointeurs. Cela évite certaines erreurs possibles.
  • Déclarez les paramètres const, si possible, en particulier les pointeurs vers des objets. De cette façon, les objets ne peuvent pas être libérés "accidentellement" (sauf si vous lancez le const ;-))).
  • Réduisez le nombre d'emplacements dans le programme où vous effectuez l'allocation et la désallocation de mémoire. Par exemple. si vous allouez ou libérez plusieurs fois le même type, écrivez-lui une fonction (ou une méthode d'usine ;-)).
    De cette façon, vous pouvez créer facilement une sortie de débogage (quelles adresses sont allouées et désallouées, ...) si nécessaire.
  • Utilisez une fonction de fabrique pour allouer des objets de plusieurs classes associées à partir d'une seule fonction.
  • Si vos classes ont une classe de base commune avec un destructeur virtuel, vous pouvez toutes les libérer en utilisant la même fonction (ou méthode statique).
  • Vérifiez votre programme avec des outils comme purifier (malheureusement beaucoup de $ / € / ...).
mh.
la source
0

Vous pouvez intercepter les fonctions d'allocation de mémoire et voir s'il y a des zones de mémoire non libérées à la sortie du programme (bien que cela ne convienne pas à toutes les applications).

Cela peut également être fait au moment de la compilation en remplaçant les opérateurs new et delete et d'autres fonctions d'allocation de mémoire.

Par exemple, vérifiez dans ce site [Débogage de l'allocation de mémoire en C ++] Remarque: Il existe une astuce pour l'opérateur de suppression qui ressemble également à ceci:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

Vous pouvez stocker dans certaines variables le nom du fichier et quand l'opérateur de suppression surchargé saura de quel endroit il a été appelé. De cette façon, vous pouvez avoir la trace de chaque suppression et malloc de votre programme. À la fin de la séquence de vérification de la mémoire, vous devriez être en mesure de signaler quel bloc de mémoire alloué n'a pas été «supprimé» en l'identifiant par nom de fichier et numéro de ligne, ce que je suppose que vous voulez.

Vous pouvez également essayer quelque chose comme BoundsChecker sous Visual Studio qui est assez intéressant et facile à utiliser.

INS
la source
0

Nous enveloppons toutes nos fonctions d'allocation avec une couche qui ajoute une brève chaîne à l'avant et un drapeau sentinelle à la fin. Ainsi, par exemple, vous auriez un appel à "myalloc (pszSomeString, iSize, iAlignment); ou new (" description ", iSize) MyObject (); qui alloue en interne la taille spécifiée plus suffisamment d'espace pour votre en-tête et votre sentinelle. Bien sûr , n'oubliez pas de commenter ceci pour les versions non déboguées! Cela prend un peu plus de mémoire pour faire cela mais les avantages l'emportent largement sur les coûts.

Cela présente trois avantages: tout d'abord, cela vous permet de suivre facilement et rapidement le code qui fuit, en effectuant des recherches rapides de code alloué dans certaines «zones» mais non nettoyé lorsque ces zones auraient dû être libérées. Il peut également être utile de détecter lorsqu'une limite a été écrasée en vérifiant que toutes les sentinelles sont intactes. Cela nous a sauvé de nombreuses fois en essayant de trouver ces plantages bien cachés ou faux pas de tableau. Le troisième avantage est de suivre l'utilisation de la mémoire pour voir qui sont les grands joueurs - une compilation de certaines descriptions dans un MemDump vous indique quand le `` son '' prend beaucoup plus de place que vous ne l'aviez prévu, par exemple.

écran
la source
0

C ++ est conçu RAII à l'esprit. Il n'y a vraiment pas de meilleur moyen de gérer la mémoire en C ++, je pense. Mais veillez à ne pas allouer de très gros morceaux (comme des objets tampons) sur la portée locale. Cela peut provoquer des débordements de pile et, s'il y a une faille dans la vérification des limites lors de l'utilisation de ce bloc, vous pouvez écraser d'autres variables ou renvoyer des adresses, ce qui conduit à toutes sortes de failles de sécurité.

artificiel
la source
0

L'un des seuls exemples d'allocation et de destruction à différents endroits est la création de threads (le paramètre que vous passez). Mais même dans ce cas, c'est facile. Voici la fonction / méthode créant un thread:

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

Ici à la place la fonction thread

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

Assez facile n'est-ce pas? En cas d'échec de la création du thread, la ressource sera libérée (supprimée) par auto_ptr, sinon la propriété sera transmise au thread. Que faire si le thread est si rapide qu'après sa création, il libère la ressource avant le

param.release();

est appelé dans la fonction / méthode principale? Rien! Parce que nous allons 'dire' à auto_ptr d'ignorer la désallocation. La gestion de la mémoire C ++ est-elle facile, n'est-ce pas? À votre santé,

Ema!

Emanuele Oriani
la source
0

Gérez la mémoire de la même manière que vous gérez les autres ressources (descripteurs, fichiers, connexions db, sockets ...). GC ne vous aiderait pas non plus.

Nemanja Trifunovic
la source
-3

Exactement un retour de n'importe quelle fonction. De cette façon, vous pouvez faire une désallocation là-bas et ne jamais la manquer.

Sinon, il est trop facile de se tromper:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.
Seth Morris
la source
Votre réponse ne correspond pas à l'exemple de code ici? Je suis d'accord avec la réponse «un seul retour», mais l'exemple de code montre ce qu'il ne faut PAS faire.
simon le
1
Le but de C ++ RAII est exactement d'éviter le type de code que vous avez écrit. En C, c'est probablement la bonne chose à faire. Mais en C ++, votre code est défectueux. Par exemple: Et si new b () lance? Vous perdez un fichier.
paercebal le