Question

Quelqu'un connaît-il une " technique " découvrir des fuites de mémoire causées par des pointeurs intelligents? Je travaille actuellement sur un grand projet écrit en C ++ , qui utilise beaucoup de pointeurs intelligents avec comptage de références. De toute évidence, nous avons des fuites de mémoire causées par des pointeurs intelligents, qui sont toujours référencés quelque part dans le code, de sorte que leur mémoire ne soit pas libérée. Il est très difficile de trouver la ligne de code avec le caractère & "Inutile" & "; référence, l’objet correspondant n’est pas libre (bien qu’il ne soit plus utile).

J'ai trouvé des conseils sur le Web, qui proposaient de collecter des piles d'appels des opérations d'incrémentation / décrémentation du compteur de référence. Cela me donne une bonne idée du code qui a entraîné l’augmentation ou la diminution du compteur de références.

Mais il me faut un algorithme qui regroupe les " augmenter / diminuer les piles d’appel " ensemble. Après avoir retiré ces paires de piles d’appels, j’espère avoir (au moins) un & Quot; augmenter la pile d’appel & "; reste, cela me montre le morceau de code avec le & "inutile" & "; référence, qui a empêché l'objet correspondant d'être libéré. Maintenant, ce ne sera pas grave de réparer la fuite!

Mais quelqu'un a-t-il une idée pour un " algorithme " que fait le groupement?

Le développement s'effectue sous Windows XP .

(J'espère que quelqu'un a compris ce que j'ai essayé d'expliquer ...)

EDIt: Je parle de fuites causées par des références circulaires.

Était-ce utile?

La solution

Notez que les dépendances circulaires constituent une source de fuites avec les pointeurs intelligents de comptage de références . Par exemple, A a un pointeur intelligent sur B et B a un pointeur intelligent sur A. Ni A ni B ne seront détruits. Vous devrez trouver, puis casser les dépendances.

Si possible, utilisez des pointeurs intelligents «boost» et «shared_ptr» pour les pointeurs supposés être les propriétaires des données et «faiblesse_ptr» pour les pointeurs non supposés appeler suppression.

Autres conseils

Voici comment je le fais: - sur chaque pile d'appels d'enregistrement AddRef (), - Matching Release () le supprime. Ainsi, à la fin du programme, il me reste AddRefs () sans maching Releases. Pas besoin de faire correspondre les paires,

Si vous pouvez reproduire la fuite de manière déterministe, une technique simple que j’ai souvent utilisée consiste à numéroter tous vos pointeurs intelligents dans leur ordre de construction (utilisez un compteur statique dans le constructeur) et à signaler cet ID avec la fuite. . Puis relancez le programme et déclenchez un DebugBreak () lorsque le pointeur intelligent avec le même ID est construit.

Vous devriez également envisager cet outil formidable: http: //www.codeproject.com/KB/applications/visualleakdetector.aspx

Pour détecter les cycles de référence, vous devez disposer d'un graphique de tous les objets comptés. Un tel graphique n’est pas facile à construire, mais c’est faisable.

Créez un set<CRefCounted*> global pour enregistrer les objets vivants comptés en référence. C’est plus facile si vous avez une implémentation courante AddRef () - ajoutez simplement un this pointeur à l’ensemble lorsque le nombre de références de l’objet passe de 0 à 1. De même, dans Release (), supprimez un objet de l’ensemble lorsque son nombre de références passe de 1 à 0.

Ensuite, indiquez un moyen d'obtenir l'ensemble des objets référencés de chaque CRefCounted*. Ce pourrait être un virtual set<CRefCounted*> CRefCounted::get_children() ou ce qui vous convient. Vous avez maintenant un moyen de parcourir le graphique.

Enfin, implémentez votre algorithme favori pour la détection de cycle dans un graphe orienté . Démarrez le programme, créez des cycles et lancez le détecteur de cycle. Prendre plaisir! :)

Ce que je fais consiste à envelopper le pointeur intelligent dans une classe prenant les paramètres FUNCTION et LINE . Incrémentez un compte pour cette fonction et cette ligne chaque fois que le constructeur est appelé et décrémentez-le chaque fois que le destructeur est appelé. écrivez ensuite une fonction qui vide les informations fonction / ligne / compte. Cela vous indique où toutes vos références ont été créées

Pour résoudre ce problème, j'ai remplacé le malloc / new &. libérer / supprimer afin qu'ils gardent autant que possible dans une structure de données une trace de l'opération que vous effectuez.

Par exemple, lors de la substitution de malloc / new , vous pouvez créer un enregistrement de l'adresse de l'appelant, du nombre d'octets demandés, de la valeur de pointeur attribuée renvoyée et d'un ID de séquence afin que tous vos enregistrements puissent être conservés. séquencé (je ne sais pas si vous traitez avec des threads mais vous devez aussi en tenir compte).

Lors de l'écriture des routines libérer / supprimer , je garde également une trace de l'adresse de l'appelant et des informations du pointeur. Ensuite, je regarde en arrière dans la liste et j'essaie de faire correspondre le correspondant malloc / new en utilisant le pointeur comme clé. Si je ne le trouve pas, élevez un drapeau rouge.

Si vous en avez les moyens, vous pouvez incorporer dans vos données l'ID de séquence pour savoir avec certitude à qui et à quel moment l'appel a été attribué. La clé ici est d’identifier chaque paire de transaction autant que possible.

Ensuite, vous aurez une troisième routine affichant votre historique des allocations de mémoire / désallocation, ainsi que les fonctions appelant chaque transaction. (Ceci peut être accompli en analysant la carte symbolique de votre éditeur de liens). Vous saurez combien de mémoire vous aurez alloué à tout moment et qui l'a fait.

Si vous ne disposez pas de suffisamment de ressources pour effectuer ces transactions (cas typique des microcontrôleurs 8 bits), vous pouvez transmettre les mêmes informations via un lien série ou TCP à un autre ordinateur disposant de suffisamment de ressources.

Il ne s’agit pas de trouver une fuite. Dans le cas de pointeurs intelligents, il sera probablement dirigé vers un emplacement générique tel que CreateObject (), appelé des milliers de fois. Il s’agit de déterminer quel emplacement dans le code n’appelle pas Release () sur un objet compté en référence.

Puisque vous avez dit que vous utilisiez Windows, vous pourrez peut-être tirer parti de l'utilitaire de vidage de mémoire en mode utilisateur de Microsoft, UMDH , fourni avec le Outils de débogage pour Windows . UMDH crée des instantanés de l'utilisation de la mémoire de votre application, en enregistrant la pile utilisée pour chaque allocation, et vous permet de comparer plusieurs instantanés pour voir quels appels de l'allocateur & "Ont subi une fuite &"; Mémoire. Il traduit également les traces de la pile en symboles pour vous à l'aide de dbghelp.dll.

Il existe également un autre outil Microsoft appelé " LeakDiag " cela supporte plus d'allocateurs de mémoire que UMDH, mais c'est un peu plus difficile à trouver et ne semble pas être maintenu activement. La dernière version date d'au moins cinq ans, si je me souviens bien.

Si j'étais vous, je prendrais le journal et rédigerais un script rapide pour faire quelque chose comme ce qui suit (le mien est en Ruby):

def allocation?(line)
  # determine if this line is a log line indicating allocation/deallocation
end

def unique_stack(line)
  # return a string that is equal for pairs of allocation/deallocation
end

allocations = []
file = File.new "the-log.log"
file.each_line { |line|
  # custom function to determine if line is an alloc/dealloc
  if allocation? line
    # custom function to get unique stack trace where the return value
    # is the same for a alloc and dealloc
    allocations[allocations.length] = unique_stack line
  end
}

allocations.sort!

# go through and remove pairs of allocations that equal,
# ideally 1 will be remaining....
index = 0

while index < allocations.size - 1
  if allocations[index] == allocations[index + 1]
    allocations.delete_at index
  else
    index = index + 1
  end
end

allocations.each { |line|
  puts line
}

Cela passe essentiellement par le journal et capture chaque allocation / désallocation et stocke une valeur unique pour chaque paire, puis triez-la et supprimez les paires qui correspondent, voyez ce qui reste.

Mise à jour: Désolé pour toutes les modifications intermédiaires (j'ai posté par inadvertance avant d'avoir terminé)

Pour Windows, consultez:

Détection de fuite de mémoire MFC

Je suis un grand fan de Le vérificateur de tas de Google - ce ne sera pas le cas attraper toutes les fuites, mais il en obtient la plupart. ( Conseil : associez-le à tous vos centres d'intérêt.)

La première étape pourrait être de savoir quelle classe fuit. Une fois que vous le savez, vous pouvez trouver qui augmente la référence: 1. Placez un point d'arrêt sur le constructeur de la classe encapsulé par shared_ptr. 2. intervenez avec le débogueur dans shared_ptr quand il augmente le nombre de références: regardez la variable pn - & Gt; pi _- & Gt; use_count_ Prenez l'adresse de cette variable en évaluant l'expression (quelque chose comme ceci: & Et ceci - & Gt; pn - & Gt; pi_.use_count_), vous obtiendrez une adresse. 3. Dans Visual Studio Debugger, accédez à Debug - & Gt; Nouveau point d'arrêt - & Gt; Nouveau point d'arrêt de données ... Entrez l'adresse de la variable 4. Exécutez le programme. Votre programme s’arrêtera à chaque fois que le code augmentera ou diminuera dans le code. Ensuite, vous devez vérifier si ceux-ci correspondent.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top