Утечки памяти - ужас каждого программиста? - PullRequest
21 голосов
/ 06 февраля 2011

Я программирую игровой движок на C ++, который также поддерживает Lua.

Мой самый большой ужас: утечки памяти.

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

Я их боюсь, потому что мне кажется, что их очень трудно заметить. Особенно в сложных системах. Если мой движок почти закончен, игра запускается и память уходит, что я буду делать? Где я начну искать?

  • Оправдан ли мой страх утечек памяти?
  • Как узнать, где находится утечка памяти?
  • Разве нет хороших инструментов, которые помогают найти источник утечек памяти сегодня?

Ответы [ 12 ]

36 голосов
/ 06 февраля 2011

Как узнать, где находится утечка памяти?

Valgrind

17 голосов
/ 06 февраля 2011

Необработанные указатели - только одна потенциальная причина утечек памяти.

Даже если вы используете умные указатели, такие как shared_ptr, вы можете получить утечку, если у вас есть цикл - лекарство от этого - где-то использовать слабый_птр, чтобы разорвать цикл. Использование интеллектуальных указателей не является панацеей от утечек памяти.

Вы также можете забыть о виртуальном деструкторе в базовом классе и получить утечки таким образом.

Даже если нет проблем с удалением новых объектов, длительный процесс может увеличиться (и, по-видимому, протечь) из-за фрагментации адресного пространства.

Такие инструменты, как valgrind, очень и очень полезны для поиска утечек, но они не всегда сообщают вам, где должно быть исправление (например, в случае циклов или объектов, удерживающих умные указатели)

13 голосов
/ 06 февраля 2011

Требуется четко определенная модель «времени жизни объекта».Каждый раз, когда вы делаете «новый», вам нужно думать о

  1. Кто владеет этим объектом кучи?т.е. кто отвечает за поддержание этого указателя и разрешение другим "клиентам" ссылаться на него?

  2. Кто отвечает за удаление этого объекта?Обычно это # ​​1, но не обязательно.

  3. Есть ли клиенты этого объекта, чье время жизни больше, чем у этого объекта?Если это так, и они на самом деле хранят этот указатель кучи, он разыменовывает память, которая больше не «существует».Вам может потребоваться добавить некоторые механизмы уведомления или изменить дизайн модели «времени жизни объекта».

Часто вы исправляете утечки памяти, но затем сталкиваетесь с проблемой № 3.Вот почему лучше продумать модель времени жизни вашего объекта, прежде чем писать слишком много кода.

10 голосов
/ 06 февраля 2011

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

7 голосов
/ 07 февраля 2011

Мой страх перед утечками памяти оправдан?

Короткий ответ - да. Длинный ответ - да, но есть методы их уменьшения и, как правило, упрощения. Самым важным из этих вещей, по моему мнению, не является легкое использование new / delete и разработка вашей программы для уменьшения или уменьшения как можно большего динамического распределения. Вместо того, чтобы выделять память, попробуйте следующее и выделяйте свою собственную память только тогда, когда ни один из этих методов не работает для вас (примерно в порядке, который вы предпочитаете):

  • Распределить в стеке
  • Используйте стандартные контейнеры (C ++ Standard Library и / или Boost), например, вместо написания собственного связанного списка, используйте std :: list
  • В связи с вышеизложенным сохраняйте объекты по значению, если можете (то есть, если создание копии не слишком дорого), или, по крайней мере, ссылки (со ссылками, которые вам не нужно беспокоиться о нулевом значении) для размещения выделенных объектов
  • Передача ссылок на объекты стека, где это возможно
  • Когда вам нужно выделить свою собственную память, попробуйте использовать RAII для выделения в конструкторе и снова освободить его в деструкторе. Убедитесь, что этот объект расположен в стеке.
  • Когда вам нужно использовать указатели для распределения данных вручную, используйте умные указатели для автоматического освобождения объектов, которые больше не используются
  • Четко определите, каким объектам принадлежит какая память. Постарайтесь ограничить вашу иерархию классов как можно меньшим количеством владельцев ресурсов и позволить им заниматься распределением и освобождением объектов для вас
  • Если вам нужен более общий доступ к динамической памяти, напишите менеджер ресурсов, который станет владельцем объектов. Система дескрипторов , как описано здесь , также полезна, поскольку она позволяет вам «собирать мусор» в памяти, которая больше не нужна. Дескрипторы также могут быть помечены тем, какой подсистеме принадлежит какая память, поэтому вы можете вывести состояние использования системной памяти, например, «подсистеме А принадлежит 32% выделенной памяти»
  • Перегрузка операторов new и delete для выделенных классов, чтобы вы могли поддерживать дополнительные метаданные о том, кто / что / где / когда выделил, что

Как узнать, где память Утечка лжи?

Valgrind набор инструментов : Memcheck, Cachegrind, Callgrind, Massif, Helgrind ...

Вы также можете попробовать скомпилировать с electric fence (-lefence for gcc) или вашими компиляторами. Вы также можете попробовать набор инструментов Intel, особенно если вы пишете многопоточный код или чувствительный к производительности код (например, Parallel Studio), хотя они и дороги.

Нет хороших инструментов, которые помогают в найти источник утечек памяти сегодня

Конечно, есть. Смотри выше.

Теперь, так как вы пишете игру, я поделюсь некоторыми своими мыслями / опытом по разработке игр:

  • Предварительно выделите как можно больше (в идеале все) в начале каждого уровня. Тогда во время уровня вам не нужно беспокоиться об утечках памяти. Сохраняйте указатели на все, что вы выделили, и в начале следующего уровня освобождайте их все, так как не должно быть возможности существования висячих указателей, когда следующий уровень загружает свой собственный чистый набор данных.
  • Используйте распределители стека (либо используя фактический стек, либо создавая свой собственный в куче), чтобы управлять распределениями внутри уровня или фрейма. Когда вам нужна память, вытолкните ее из верхней части стека. Затем, когда этот уровень или кадр будет завершен, вы можете просто очистить стек (если вы храните только типы POD, это быстро и просто: просто сбросьте указатель стека. Я использую это для размещения сообщений или системы обмена сообщениями в моем собственном игровом движке : во время кадра сообщения (типы POD фиксированного размера в моем движке) распределяются из стекового пула памяти (чья память предварительно выделена). Все события просто берутся из стека. В конце кадра я «поменять» стек на второй (чтобы обработчики событий могли также отправлять события) и вызвать каждый обработчик событий. Наконец, я просто сбрасываю указатель. Это делает распределение сообщений очень быстрым и невозможным утечку памяти.
  • Используйте систему ресурсов (с дескрипторами) для всех ресурсов, которыми вы не можете управлять через пулы памяти или распределители стека: буферы, данные уровня, изображения, аудио. Моя система ресурсов, например, также поддерживает потоковые ресурсы в фоновом режиме. Дескрипторы создаются немедленно, но помечаются как «не готовы», пока ресурс не завершил потоковую передачу.
  • Создайте ваши данные, а не код (то есть сначала спроектируйте структуры данных). По возможности изолируйте выделение памяти.
  • Попробуйте сохранить похожие данные вместе . Это не только облегчит управление его временем жизни, но также может улучшить производительность ваших движков за счет лучшего использования кэша (например, все позиции символов хранятся в контейнере позиций, все позиции обновляются / обрабатываются вместе и т. Д.).
  • Наконец, если вы можете программировать в максимально возможном чисто функциональном стиле, вместо того, чтобы слишком полагаться на ООП, вы можете упростить ряд проблем: управление памятью проще, потому что то, какие части вашего кода могут изменять данные, ограничено. Распределение происходит перед вызовом функций, освобождение - когда они завершены (конвейер потоков вызовов функций). Во-вторых, если вы имеете дело с чисто функциональным кодом, работающим с неизменяемыми данными, многоядерное программирование будет значительно упрощено. Двойная победа. Например, в моем движке данные игровых объектов обрабатываются чисто функциональным кодом, который принимает в качестве входных данных текущее состояние игры и возвращает в качестве выходных данных следующее игровое состояние кадров. Это позволяет очень легко отследить, какие части кода могут выделить или освободить память, и, как правило, отследить время жизни объектов. Из-за этого я могу параллельно обрабатывать игровые объекты.

Надеюсь, это помогло.

6 голосов
/ 07 февраля 2011

AFAIK Valgrid - это только Linux.
Для Windows у вас есть такие инструменты, как BoundsChecker и Purify .
Если вы используете Visual Studio, библиотека C Runtime (CRT) также предоставляет удивительно простой и полезный инструмент для обнаружения утечек памяти из коробки.Прочитайте о _CrtDumpMemoryLeaks и связанных с ним функциях и макросах.
В основном это позволяет получить индексированный дамп утечек памяти при выходе из процесса, а затем позволяет установить точку останова во время утечки.память была выделена, чтобы увидеть, когда именно это произошло.Это в отличие от большинства других инструментов, которые дают вам только посмертный анализ без способа воспроизвести события, которые привели к утечке памяти.
Использование этих маленьких драгоценных камней с первого дня дает вам относительное спокойствие, что вы 'в хорошей форме.

4 голосов
/ 06 февраля 2011

Мой страх перед утечками памяти оправдан?

Если вы пишете код, который их содержит, то абсолютно.

Как узнать, где находится утечка памяти?

Путем анализа кода.

Разве нет хороших инструментов, которые помогают найти источник утечек памяти сегодня?

Да, но это все еще не просто.

Хорошая новость в том, что при правильном дизайне это совершенно не нужно . Унция предотвращения стоит тонны выздоровления: лучшая стратегия борьбы с утечками памяти - это написать код таким образом, чтобы гарантировать, что его нет.

3 голосов
/ 06 февраля 2011

Риск звучать как самодовольный придурок, который я, вероятно, представляю, рассмотрите возможность использования любого языка программирования, разработанного после 1979, у которого нет проблем с утечками памяти, повреждением стека кучи или даже памятьюуправление.(Любой, кто говорит что-то вроде «Мне нужна моя программа, чтобы быть быстрым», вероятно, никогда не слышал о Дональде Кнуте .)

2 голосов
/ 07 февраля 2011

По крайней мере для части Lua вы можете использовать свой собственный распределитель памяти и отслеживать все выделения и освобождения и таким образом обнаруживать любые утечки памяти.

2 голосов
/ 06 февраля 2011

Утечки памяти не слишком страшны - но они могут нанести ущерб производительности программы (так что избавьтесь от них!).

  1. Не бойтесь утечек памяти. Подумайте, что это такое - память, которая не была удалена, но к которой удален весь доступ (поэтому, пока выполнение не закончится, система не знает, что может перераспределить эту память).
  2. Вы можете обнаружить утечки памяти, так сказать, «вручную», просматривая ваши объекты и убедившись, что они удалены в нужном месте (и только один раз! В противном случае возникнут другие ошибки). Такие инструменты, как Valgrind , могут помочь определить причину ошибки.
  3. Как уже упоминалось ранее (и я упоминал выше) Valgrind - отличный инструмент для обнаружения утечек памяти. Запуск это так:

valgrind --leak-check=full -v ./YOUR_EXECUTABLE

Это даст вам полную проверку на утечку и подробный вывод (-v) о том, как используется память в вашей программе.

С уважением,
Деннис М.

...