Найти утечки памяти, вызванные умными указателями - PullRequest
17 голосов
/ 16 сентября 2008

Кто-нибудь знает «технику» для обнаружения утечек памяти, вызванных умными указателями? В настоящее время я работаю над большим проектом, написанным на C ++ , в котором интенсивно используются умные указатели со счетчиком ссылок. Очевидно, у нас есть некоторые утечки памяти, вызванные умными указателями, на которые все еще ссылаются где-то в коде, так что их память не освобождается. Очень трудно найти строку кода с «ненужной» ссылкой, которая приводит к тому, что соответствующий объект не будет свободным (хотя он больше не используется).

В сети я нашел несколько советов, которые предлагали собирать стеки вызовов операций увеличения / уменьшения счетчика ссылок. Это дает мне хороший совет, какой фрагмент кода вызвал увеличение или уменьшение счетчика ссылок.

Но мне нужен какой-то алгоритм, который группирует соответствующие «стеки вызовов увеличения / уменьшения» вместе. После удаления этих пар стеков вызовов, я надеюсь, у меня остался (по крайней мере) один «увеличенный стек вызовов», который показывает мне фрагмент кода с «ненужной» ссылкой, который вызвал освобождение соответствующего объекта. Теперь будет несложно исправить утечку!

Но есть ли у кого-нибудь идея для "алгоритма", который делает группировку?

Разработка происходит под Windows XP .

(надеюсь, кто-то понял, что я пытался объяснить ...)

EDIt: я говорю о утечках, вызванных циклическими ссылками.

Ответы [ 12 ]

16 голосов
/ 17 сентября 2008

Обратите внимание, что одним источником утечек с умными указателями подсчета ссылок являются указатели с круговыми зависимостями . Например, A имеет интеллектуальный указатель на B, а B имеет интеллектуальный указатель на A. Ни A, ни B не будут уничтожены. Вам придется найти, а затем разорвать зависимости.

Если возможно, используйте умные указатели boost и используйте shared_ptr для указателей, которые должны быть владельцами данных, и weak_ptr для указателей, которые не должны вызывать delete.

6 голосов
/ 16 сентября 2008

Я делаю это просто: - на каждом стеке вызовов AddRef (), - Соответствующий Release () удаляет его. Таким образом, в конце программы у меня остается AddRefs () без обработки релизов. Нет необходимости подбирать пары,

4 голосов
/ 10 октября 2008

Чтобы обнаружить эталонные циклы, вам нужно иметь график всех подсчитанных эталонных объектов. Такой график нелегко построить, но это возможно.

Создать глобальный set<CRefCounted*> для регистрации живых объектов с подсчетом ссылок. Это проще, если у вас есть общая реализация AddRef () - просто добавьте указатель this к набору, когда счетчик ссылок объекта изменяется с 0 на 1. Аналогично, в Release () удаляют объект из набора, когда его счетчик ссылок изменяется с 1 на 0.

Далее, предоставьте некоторый способ получить набор ссылочных объектов от каждого CRefCounted*. Это может быть virtual set<CRefCounted*> CRefCounted::get_children() или все, что вам подходит. Теперь у вас есть возможность пройтись по графику.

Наконец, реализуйте ваш любимый алгоритм для обнаружения цикла в ориентированном графе . Запустите программу, создайте несколько циклов и запустите детектор циклов. Наслаждайтесь! :)

4 голосов
/ 16 сентября 2008

Я оборачиваю умный указатель классом, который принимает FUNCTION и LINE . Увеличивайте счетчик для этой функции и строки каждый раз, когда вызывается конструктор, и уменьшайте счетчик каждый раз, когда вызывается деструктор. затем напишите функцию, которая выводит информацию о функции / строке / числе. Это говорит вам, где были созданы все ваши ссылки

4 голосов
/ 16 сентября 2008

Если вы можете воспроизвести утечку детерминированным способом, я часто использовал простую технику для нумерации всех ваших интеллектуальных указателей в порядке их построения (используйте статический счетчик в конструкторе) и сообщал этот идентификатор вместе с утечкой , Затем снова запустите программу и запустите DebugBreak (), когда будет создан интеллектуальный указатель с тем же идентификатором.

Вы также должны рассмотреть этот замечательный инструмент: http://www.codeproject.com/KB/applications/visualleakdetector.aspx

2 голосов
/ 16 сентября 2008

Это не вопрос обнаружения утечки. В случае умных указателей он, скорее всего, будет направлен в некое общее место, например CreateObject (), которое вызывается тысячи раз. Необходимо определить, какое место в коде не вызывало Release () для объекта с пересчетом.

2 голосов
/ 16 сентября 2008

Поскольку вы сказали, что используете Windows, возможно, вы сможете воспользоваться утилитой дамп-памяти Microsoft, UMDH , которая поставляется с Средствами отладки для Windows . UMDH делает снимки использования памяти вашего приложения, записывает стек, используемый для каждого выделения, и позволяет сравнивать несколько снимков, чтобы увидеть, какие вызовы распределителю «просочились» в память. Он также переводит трассировки стека в символы для вас, используя dbghelp.dll.

Существует также другой инструмент Microsoft под названием «LeakDiag», который поддерживает больше распределителей памяти, чем UMDH, но его немного сложнее найти и, похоже, он не поддерживается активно. Последней версии, по крайней мере, пять лет, если я правильно помню.

2 голосов
/ 16 сентября 2008

Чтобы решить эту проблему, я переопределил операторы malloc / new & free / delete , чтобы они как можно больше отслеживали структуру данных об операции Вы выполняете.

Например, при переопределении malloc / new , вы можете создать запись адреса вызывающего абонента, количества запрошенных байтов, возвращенного назначенного значения указателя и идентификатора последовательности, чтобы все ваши записи могли быть упорядочены (Я не знаю, имеете ли вы дело с темами, но вам также нужно это учитывать).

При записи подпрограмм free / delete я также отслеживаю адрес вызывающего абонента и информацию об указателе. Затем я оглядываюсь назад в список и пытаюсь сопоставить копию malloc / new , используя указатель в качестве моего ключа. Если я не найду его, поднимите красный флаг.

Если вы можете себе это позволить, вы можете встраивать в свои данные идентификатор последовательности, чтобы быть абсолютно уверенным, кому и когда был сделан вызов распределения. Ключевым моментом здесь является уникальная идентификация каждой пары транзакций как можно больше.

Тогда у вас будет третья подпрограмма, отображающая историю распределения / освобождения памяти вместе с функциями, вызывающими каждую транзакцию. (это может быть достигнуто путем анализа символической карты из вашего компоновщика). Вы будете знать, сколько памяти вы выделите в любое время и кто это сделал.

Если у вас недостаточно ресурсов для выполнения этих транзакций (мой типичный случай для 8-разрядных микроконтроллеров), вы можете вывести ту же информацию через последовательный канал или канал TCP на другой компьютер с достаточным количеством ресурсов.

1 голос
/ 16 сентября 2008

Я большой поклонник Google Heapchecker - он не будет ловить все утечки, но он получает большинство из них. ( Совет : свяжите это со всеми вашими юнит-тестами.)

1 голос
/ 16 сентября 2008
...