11
votes

Comment se protéger des fuites de mémoire?

J'étais récemment interviewé pour une position C ++ et on m'a demandé comment je me garde contre la création de fuites de mémoire. Je sais que je n'ai pas donné une réponse satisfaisante à cette question, alors je vous le jette à vous les gars. Quels sont les meilleurs moyens de se protéger des fuites de mémoire?

merci!


5 commentaires

Utilisez un collecteur de déchets ( Google.com/search?q=garbage + Collection + C% 2B% 2B ) ...?


@KennyTM Non, n'utilisez pas de collecteur de déchets, quand vous avez Raii. Si vous avez vraiment besoin d'une propriété partagée, utilisez simplement Shared_Ptr de C ++ 0x ou Boost actuellement.


@Kenny: Si vous voulez vivre avec les problèmes associés à GC. C ++ a un bien meilleur mécanisme de contrôle grainé raffiné appelé Pointeurs intelligents.


Merci à tous pour les réponses approfondies. Décider quelle réponse à accepter a été difficile. Puisque la réponse de Poita_ a tant de upvotes, je laisserai celui-là parler de lui-même. J'ai marqué la réponse de Jalf comme réponse puisqu'il a des considérations académiques de ses points. Merci encore!


@KennyTM: Un GC en C ++ ne résout pas le problème. Un GC en C ++ doit faire une estimation pire des cas très conservatrice de la mémoire utilisée, ce qui signifie que pourrait manquer certaines allocations . Donc, en C ++, un GC n'élimine pas les fuites de mémoire, elle prend juste soin de certains d'entre eux.


14 Réponses :


2
votes

Utilisez toutes sortes de pointeurs intelligents.

Utilisez une certaine stratégie de création et de suppression d'objets, comme qui crée qui est responsable de la suppression.


0 commentaires

7
votes

Remplacez NOUVEAU avec Shared_PTR. Fondamentalement Raii. faire un coffre-fort de code de code. Utilisez la STL partout possible. Si vous utilisez des pointeurs de comptage de référence, assurez-vous qu'ils ne forment pas de cycles. Scoped_exit de Boost est également très utile.


4 commentaires

Veuillez illustrer comment vous remplaceriez «Nouveau» avec un pointeur partagé.


boost :: partagé_ptr pTR = boost :: partagé_ptr (nouveau t ());


Qui utilise de nouveau - remplaçant à peine quoi que ce soit.


Je dirais que Boost :: Shared_ptr PTR (nouveau t); ferait la même chose mais sauvegarder une création d'objet Temp et copier



1
votes

0 commentaires

1
votes

Un très bon moyen utilise des pointeurs intelligents, le boost / tr1 :: partagé_ptr. La mémoire sera libre, une fois que le pointeur intelligent (pile alloué) est hors de portée.


0 commentaires

0
votes
  • Pointeurs intelligents.
  • Gestion de la mémoire.
  • Remplacez "Nouveau" et "Supprimer" ou utilisez vos propres macros / modèles.

0 commentaires

3
votes
  1. ( facile ) ne laissez jamais un pointeur brut posséder un objet (recherchez votre code pour le REGEXP "\ = * nouveau" . Utilisez . Shared_ptr ou scoped_ptr au lieu de cela, voire mieux, utilisez de vraies variables au lieu des pointeurs aussi souvent que possible.

  2. ( dur ) Assurez-vous de ne pas avoir de références circulaires, avec Shared_Ptrs pointant les uns aux autres, utilisez faibles_ptr pour les casser.

    fait!


0 commentaires

20
votes
  1. n'acitez pas la mémoire sur le tas si vous n'avez pas besoin de. La plupart des travaux peuvent être effectués sur la pile, de sorte que vous ne devriez donc faire que des allocations de mémoire de tas lorsque vous en avez absolument besoin.

  2. Si vous avez besoin d'un objet alloué en tas appartenant à un seul autre objet, utilisez std :: auto_ptr .

  3. Utilisez des conteneurs standard ou des conteneurs de boost au lieu d'inventer votre propre.

  4. Si vous avez un objet qui est appelé par plusieurs autres objets et appartient à personne en particulier en particulier, utilisez soit std :: tr1 :: partagé_ptr ou std :: tr1 :: faibly_ptr - quel que soit le cas de votre utilisation.

  5. Si aucune de ces choses ne correspond à votre cas d'utilisation, utilisez peut-être Supprimer . Si vous finissez par avoir à gérer manuellement la mémoire, utilisez simplement des outils de détection de fuite de mémoire pour vous assurer que vous ne fuyez rien (et bien sûr, faites attention). Vous ne devriez jamais vraiment arriver à ce point cependant.


15 commentaires

-1 Les personnes qui écrivent leurs propres conteneurs sait ce qu'ils font et ne doivent pas avoir besoin de ne pas les utiliser pour des causes de fuite de mémoire.


@Viktor: Non, de nombreuses personnes écrivent leurs propres conteneurs sans savoir ce qu'ils font. Le fait qu'une petite minorité de programmeurs C ++ puisse écrire correctement les conteneurs ne signifie pas que la grande majorité ne bénéficiera pas d'être informé de cesser et d'utiliser les standards.


STD :: Auto_PTR est obsolète. Utilisez std :: unique_ptr à la place.


Je ne vois pas pourquoi ces outils de haut niveau sont meilleurs alors neuf / Supprimer avec certaines réflexions avant d'écrire le code, de planifier vos objets et des interactions. J'ai utilisé Valgrind dans certains de mes projets d'écoles et je suppose que plus j'utilise de nouveau / Supprimer, plus je suis prudent de ne pas faire des erreurs en oubliant de libérer de la mémoire. Boost est définitivement génial, mais je ne pense pas que je devrais compter sur elle pour libérer ma mémoire. Si j'étais un tel programmeur qui ne pouvait pas gérer sa propre mémoire, j'irais pour Java ou autre chose avec un collecteur à ordures intégré. Désolé si j'étais offensant. Mais c'est comme ça que je vois c ++.


@Spidey, puis vous voyez C ++ différemment que les luminaires et les leaders du champ qui sont clairement descendus du côté de Raii (et d'où la pile-allocation ou l'utilisation des pointeurs intelligents).


@Spidey: Utilisez-vous des exceptions dans votre code? Tout sauf impossible d'écrire un code de sécurité à l'exception avec un jumelage manuel de nouveau et de suppression.


@ADRIAN: unique_ptr n'existe pas dans la norme C ++ actuelle. @Spidey: aucun programmeur ne peut "gérer sa propre mémoire" dans un programme non trivial C ++, sans exploiter Raii. Bien sûr, Raii est beaucoup plus que des pointeurs intelligents, un point souvent oublié. Si vous voulez simplement dire que les pointeurs intelligents ne sont pas le Saint Graal, vous avez raison. Mais les appels «bruts» Supprimer sont erronés à tous les niveaux. La façon dont les programmeurs Sane C ++ "gèrent leur mémoire" consistent à assurer que les objets sont libérés automatiquement quand ils sont censés . Ce n'est pas tout à fait la même chose que de faire tout ce que Ref-compté avec partagé_ptr


@Spidey. Cela n'a rien à voir avec si les programmeurs "peuvent" gérer leur propre mémoire. La raison pour laquelle nous utilisons des pointeurs intelligents est parce que (a) cela facilite la tâche de taper, et (b) nous pouvons nous concentrer sur la résolution du problème à la main plutôt que d'écrire Supprimer xyz dans chacun de nos destructeurs.


@Spidey: GC a sa place qui est à coup sûr. Mais il s'agit d'une approche blunt de marteau de luge en matière de gestion de la mémoire par rapport aux scalpes fournies par C ++. Oui, il faut plus de compétences pour utiliser un scalpe qu'un marteau de luge, mais le meilleur contrôle des grains vaut l'effort (contrairement aux scalpes à base de Cint de C). Notez également qu'avec Raii (c'est-à-dire manuellement à l'aide du nouveau / Supprimer) est un resAPAY pour la catastrophe car il n'y a pas de sécurité en présence d'exceptions.


@JAlf: Je ne suis pas d'accord, et la raison est que je ajoute à ce que j'ai dit, les gens assez intelligents lorsque les conteneurs intégrés ne répondent pas à leurs besoins, n'écrivez pas à des conteneurs fuites.


@Viktor: Oui, mais je ne parle pas de personnes assez intelligentes pour savoir que les conteneurs intégrés ne répondent pas à leurs besoins. Je parle du groupe de programmeurs beaucoup plus nombreux que aveuglément lance leurs propres conteneurs, car ils ne savent pas / ne comprennent pas les STL, ou parce qu'ils souffrent de non-inventés -Le syndrome et ne faites pas confiance au code de la bibliothèque standard pour être correct et / ou performant. La faille de votre argument est que vous supposez que seuls les programmeurs qualifiés écrivent de nouvelles classes de conteneurs.


@Jalf alors nous avons une perception différente du monde. Je n'ai jamais vu personne ne comprenant pas les conteneurs stl pensant même à l'écriture de son propre conteneur (hériter - peut-être). Au moment où vous commencez même à envisager ce que Daastrastructureurs réside dans les conteneurs, vous apprenez les conteneurs STL.


@Viktor: vraiment? Chaque programmateur C ++ de départ unique écrit sa propre liste liée ou wrapper. Toutes les personnes.


Je suppose que j'ai besoin d'étudier les modèles de conception et le stl / boost alors. Merci à tous pour vos commentaires.


@jalf: Bien sûr que nous le faisons, après tout, nous nous sommes dit dans les cours d'introduction d'informatique ... Si seulement ils pouvaient également introduire de bonnes habitudes et la STL ...



0
votes

sur X86, vous pouvez utiliser régulièrement Valgrind pour vérifier votre code


0 commentaires

8
votes

Vous feriez bien de lire sur Raii .


0 commentaires

2
votes
  • Assurez-vous de comprendre exactement comment un objet sera supprimé chaque fois que vous créez un
  • Assurez-vous de comprendre qui possède le pointeur à chaque fois que l'on vous est retourné
  • Assurez-vous que vos chemins d'erreur disposent d'objets que vous avez créés de manière appropriée
  • être paranoïaque sur le ci-dessus

0 commentaires

21
votes

Quelles sont toutes les réponses à ébullition jusqu'à ce que ceci: éviter de devoir appeler Supprimer .

Chaque fois que le programmeur doit appeler Supprimer , vous avez une fuite de mémoire potentielle. Au lieu de cela, faites le Supprimer l'appel se produit automatiquement. C ++ garantit que les objets locaux ont leurs destructeurs appelés lorsqu'ils sortent de la portée. Utilisez cette garantie pour vous assurer que vos allocations de mémoire sont automatiquement supprimées.

À son plus général, cette technique signifie que chaque répartition de la mémoire doit être enveloppée à l'intérieur d'une classe simple, dont le constructeur attribue la mémoire nécessaire et le dépose le libère.

Comme il s'agit d'une technique aussi couramment utilisée et largement applicable, des classes de pointeur intelligentes ont été créées qui réduisent la quantité de code de la chaudière. Plutôt que d'attribuer la mémoire, leurs constructeurs prennent un pointeur sur l'allocation de mémoire déjà faite et la stocke. Lorsque le pointeur intelligent est hors de portée, il est capable de supprimer l'allocation.

Bien sûr, en fonction de l'utilisation, une sémantique différente peut être appelée. Avez-vous juste besoin du cas simple, où l'allocation devrait durer exactement aussi longtemps que la classe d'emballage vit? Ensuite, utilisez boost :: scoped_ptr ou, si vous ne pouvez pas utiliser boost, std :: auto_ptr . Avez-vous un nombre inconnu d'objets faisant référence à la répartition sans savoir combien de temps habitera chacun d'eux? Ensuite, la référence-compté boost :: Shared_ptr est une bonne solution.

Mais vous n'avez pas à utiliser des pointeurs intelligents. Les conteneurs de la bibliothèque standard font aussi le truc. Ils allouent de manière interine la mémoire requise pour stocker des copies des objets que vous avez placés, et ils libèrent à nouveau la mémoire lorsqu'ils sont supprimés. Donc, l'utilisateur n'a pas à appeler non plus nouveau ou Supprimer .

Il existe d'innombrables variations de cette technique, changeant la responsabilité de créer l'allocation de la mémoire initiale ou lorsque la répartition doit être effectuée.

Mais ce qu'ils ont tous en commun, c'est la réponse à votre question: l'acquisition des ressources idiom: les ressources est l'initialisation. Les allocations de mémoire sont une sorte de ressource. Les ressources doivent être acquises lorsqu'un objet est initialisé et publié par l'objet monlef, lorsqu'il est détruit.

Faites de la portée C ++ et des règles à vie effectuez votre travail pour vous. N'appelez jamais Supprimer en dehors d'un objet RAII, qu'il s'agisse d'une classe de conteneur, d'un pointeur intelligent ou d'un wrapper ad-hoc pour une seule allocation. Laissez l'objet gérer la ressource assignée.

Si tout Supprimer Les appels se produisent automatiquement, il n'y a aucun moyen que vous puissiez les oublier. Et puis il n'y a aucun moyen que vous puissiez fuir la mémoire.


5 commentaires

Notez qu'il pourrait entraîner des sessions d'entrevue maladroites avec des intervieweurs sans humour: "Comment évitez-vous les fuites de mémoire?" ... "Évitez d'écrire Supprimer!" ...


toutes les raisons de le dire;)


-1 Oui, il y a toujours des moyens d'obtenir des fuites de mémoire; dépendances circulaires.


@Viktor: Oui, un code incorrect sera toujours incorrect. Peut-être que vous pouvez me montrer une technique qui résout que le problème ?


@Viktor: Vous supposez que le comptage de référence RAII ==. Ce n'est pas le cas. Comment les dépendances circulaires empêchent mon scoped_ptr de supprimer l'objet qu'il pointe. Comptage de référence (et partagé_ptr ) est un cas particulier de Raii, et il a des faiblesses cruciales, et elle est trop utilisée. Mais cela ne change pas ce que j'ai dit, que Raii en général est le moyen d'éviter les fuites.



2
votes

En plus des conseils sur Raii, n'oubliez pas de rendre votre destructeur de classe de base virtual s'il y a des fonctions virtuelles.


0 commentaires

2
votes

Pour éviter les fuites de mémoire, ce que vous devez faire est d'avoir une notion claire et définie de qui est responsable de la suppression de tout objet alloué de manière dynamique.

C ++ permet la construction d'objets sur la pile (c'est-à-dire comme une sorte de variables locales). Ceci lie la création et la destruction Le flux de contrôle: un objet est créé lorsque l'exécution du programme atteint sa déclaration et que l'objet est détruit lorsque l'exécution échappe au bloc dans lequel cette déclaration a été faite. Chaque fois que l'allocation nécessite correspond à ce modèle, utilisez-le. Cela vous fera économiser une grande partie des problèmes.

Pour d'autres usages, si vous pouvez définir et documenter une notion de responsabilité claire, cela peut alors fonctionner correctement. Par exemple, vous avez une méthode ou une fonction qui renvoie un pointeur à un objet nouvellement attribué et vous documentez que l'appelant devient responsable de la suppression de cette instance. Documentation claire couplée à une bonne discipline de programmeur (quelque chose qui n'est pas facilement atteint!) Peut résoudre de nombreux problèmes restants de la gestion de la mémoire.

Dans certaines situations, y compris des programmeurs indisciplinés et des structures de données complexes, vous devrez peut-être recourir à des techniques plus avancées, telles que le comptage de référence. Chaque objet est attribué à un "compteur" qui est le nombre d'autres variables qui le pointent. Chaque fois qu'un code décide de ne plus pointer sur l'objet, le compteur est diminué. Lorsque le compteur atteint zéro, l'objet est supprimé. Le comptage de référence nécessite une manipulation stricte du compteur. Cela peut être fait avec des "pointeurs intelligents": ceux-ci sont un objet fonctionnel des pointeurs, mais qui ajuste automatiquement le compteur sur leur propre création et leur destruction.

Le comptage de référence fonctionne assez bien dans de nombreuses situations, mais ils ne peuvent pas gérer des structures cycliques. Ainsi, pour les situations les plus complexes, vous devez recourir à l'artillerie lourde, c'est-à-dire un ordures Collector . Celui que je lie est le GC pour C et C ++ écrit par Hans Boehm, et il a été utilisé dans certains projets plutôt gros (par exemple, Inkscape ). Le point d'un collecteur des ordures consiste à maintenir une vue globale sur l'espace mémoire complet, de savoir si une instance donnée est toujours utilisée ou non. C'est le bon outil lorsque des outils d'affichage local, tels que le comptage de référence, ne suffisent pas. On pourrait soutenir que, à ce moment-là, il faut se demander si C ++ est la bonne langue pour le problème à la main. La collecte de la poubelle fonctionne mieux lorsque la langue est coopérative (cela déverrouille une foule d'optimisations qui ne sont pas faisables lorsque le compilateur ignore ce qui se passe avec la mémoire, en tant que compilateur typique C ou C ++).

Notez que rien des techniques décrites ci-dessus ne permet au programmeur d'arrêter de penser. Même un GC peut souffrir de fuites de mémoire, car il utilise accessibilité comme une approximation de utilisation future (il existe des raisons théoriques qui impliquent qu'il n'est pas possible, en pleine généralité, détecter avec précision tous les objets qui ne seront pas utilisés par la suite). Vous devrez peut-être toujours définir des champs sur NULL pour informer le GC que vous n'abandonnerez plus d'objet via une variable donnée.


0 commentaires

1
votes

Vous pouvez utiliser l'utilitaire. Si vous travaillez sur Linux - utilisez Valgrid (c'est gratuit). Utilisez Deleaker sur Windows.


0 commentaires