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? P>
merci! p>
14 Réponses :
Utilisez toutes sortes de pointeurs intelligents. P>
Utilisez une certaine stratégie de création et de suppression d'objets, comme qui crée qui est responsable de la suppression. P>
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. p>
Veuillez illustrer comment vous remplaceriez «Nouveau» avec un pointeur partagé.
boost :: partagé_ptr
Qui utilise de nouveau - remplaçant à peine quoi que ce soit.
Je dirais que Boost :: Shared_ptr
Je commence en lisant les points suivants: https://stackoverflow.com/search Q =% 5BC% 2B% 2b% 5D + mémoire + fuite p>
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. P>
( facile strong>) ne laissez jamais un pointeur brut posséder un objet (recherchez votre code pour le REGEXP ( dur strud>) Assurez-vous de ne pas avoir de références circulaires, avec Shared_Ptrs pointant les uns aux autres, utilisez faibles_ptr em> pour les casser. P > li>
ol>
fait! P> "\ = * nouveau" code>. Utilisez . Shared_ptr em> ou scoped_ptr em> au lieu de cela, voire mieux, utilisez de vraies variables au lieu des pointeurs aussi souvent que possible. P> LI>
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. P> li>
Si vous avez besoin d'un objet alloué en tas appartenant à un seul autre objet, utilisez Utilisez des conteneurs standard ou des conteneurs de boost au lieu d'inventer votre propre. p> li>
Si vous avez un objet qui est appelé par plusieurs autres objets et appartient à personne en particulier en particulier, utilisez soit Si aucune de ces choses ne correspond à votre cas d'utilisation, utilisez peut-être std :: auto_ptr code>. p> l> li>
std :: tr1 :: partagé_ptr code> ou
std :: tr1 :: faibly_ptr code> - quel que soit le cas de votre utilisation. p> li>
Supprimer code>. 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. P> li>
ol>
-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 CODE> est obsolète. Utilisez
std :: unique_ptr code> à 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 code> 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 i>. Ce n'est pas tout à fait la même chose que de faire tout ce que Ref-compté avec
partagé_ptr code>
@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 code> 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 i> 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 ...
sur X86, vous pouvez utiliser régulièrement Valgrind pour vérifier votre code P>
Quelles sont toutes les réponses à ébullition jusqu'à ce que ceci: éviter de devoir appeler Chaque fois que le programmeur doit appeler À 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. P>
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. P>
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 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 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. P>
Mais ce qu'ils ont tous en commun, c'est la réponse à votre question: l'acquisition des ressources forte> 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. P>
Faites de la portée C ++ et des règles à vie effectuez votre travail pour vous. N'appelez jamais Si tout Supprimer code> em>. p>
Supprimer code>, vous avez une fuite de mémoire potentielle.
Au lieu de cela, faites le
Supprimer l'appel code> 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. P>
boost :: scoped_ptr code> ou, si vous ne pouvez pas utiliser boost,
std :: auto_ptr code>. 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 code> est une bonne solution. P>
nouveau code> ou
Supprimer code>. P>
Supprimer code> 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. P>
Supprimer code> 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. P>
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 i>?
@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 code> de supprimer l'objet qu'il pointe. Comptage de référence (et
partagé_ptr code>) est un cas particulier i> 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 i> est le moyen d'éviter les fuites.
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. P>
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. P>
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. P>
Pour d'autres usages, si vous pouvez définir et documenter em> 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. P>
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. P>
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 ++). P>
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é em> comme une approximation de utilisation future em> (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 code> pour informer le GC que vous n'abandonnerez plus d'objet via une variable donnée. P>
Vous pouvez utiliser l'utilitaire. Si vous travaillez sur Linux - utilisez Valgrid (c'est gratuit). Utilisez Deleaker sur Windows. P>
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 i>. Donc, en C ++, un GC n'élimine pas les fuites de mémoire, elle prend juste soin de certains i> d'entre eux.