Подтвердить что ты не робот

Найти утечки памяти, вызванные интеллектуальными указателями

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

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

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

Если бы я был вами, я бы взял журнал и быстро написал script, чтобы сделать что-то вроде следующего (мой находится в 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
}

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

Обновление: Извините за все промежуточные изменения (я случайно был опубликован до того, как я закончил)

Ответ 11

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

Ответ 12

Первый шаг может состоять в том, чтобы узнать, какой класс протекает. Как только вы это узнаете, вы можете найти, кто увеличивает ссылку: 1. Поместите точку останова на конструктор класса, который завернут shared_ptr. 2. Входите в отладчик внутри shared_ptr, когда он увеличивает счетчик ссылок: посмотрите на переменную pn- > pi _- > use_count_ Возьмите адрес этой переменной, оценив выражение (что-то вроде этого: & this- > pn- > pi_.use_count_), вы получите адрес 3. В отладчике визуальной студии перейдите в Debug- > New Breakpoint- > New Data Breakpoint... Введите адрес переменной 4. Запустите программу. Ваша программа будет останавливаться каждый раз, когда какая-то точка в коде увеличивается и уменьшает счетчик ссылок. Затем вам нужно проверить, соответствуют ли они.