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

Утечка памяти - ужас каждого программиста?

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

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

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

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

  • Является ли мой страх перед утечками памяти оправданным?
  • Как узнать, где лежит утечка памяти?
  • Нет ли хороших инструментов, помогающих найти источник утечек памяти сегодня?
4b9b3361

Ответ 1

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

Valgrind

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

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

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

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

Вы также можете попробовать выполнить компиляцию с помощью электрического забора (-lefence для gcc) или ваших компиляторов equivelent. Вы также можете попробовать набор инструментов Intels, особенно если вы пишете многопоточный код или код, чувствительный к производительности (например, Parallel Studio), хотя они дороги.

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

Конечно, есть. См. Выше.

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

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

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

Ответ 6

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

Ответ 7

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

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

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

Анализируя код.

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

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

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

Ответ 8

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

Ответ 9

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

  • Не бойтесь утечек памяти. Подумайте о том, что они представляют - память, которая не была удалена, но к которой удаляется весь доступ (до завершения исполнения система не знает, что она может перенаправить эту память).
  • Вы можете найти утечки памяти "вручную", так сказать, пройдя через свои объекты и убедившись, что они удалены в нужном месте (и только один раз, иначе появятся другие ошибки). Инструменты, такие как Valgrind, могут помочь определить, где может быть ошибка.
  • Как упоминалось ранее (и я упоминал выше) Valgrind - отличный инструмент для поиска утечек памяти. Выполнение этого:

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

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

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

Ответ 10

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

Ответ 11

Существуют различные методы отслеживания утечек памяти.

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

Если вы используете предварительно скомпилированные контейнеры все время, это не будет работать, поскольку все распределения будут находиться в контейнерах. Тогда ваши варианты:

  • Используйте локальное значение потока, чтобы идентифицировать подсистему или идентификатор класса (в отладочных сборках), которые выполняются, чтобы ваш распределитель мог определить, кто распределяет память. Вы даже можете использовать стек для отслеживания использования памяти иерархически в своем движке.
  • Фактически извлекайте стек вызовов и сохраняйте его, если ваш компилятор имеет достаточную поддержку.
  • Использовать пулы памяти для подсистем и измерять, если их размер увеличивается непропорционально. (Это также (по общему признанию, плохой) обходной путь для негерметичной памяти, так как вы можете сразу освободить весь пул, освободив также пропущенную память, если сможете.)
  • В Windows есть несколько макросов, которые автоматически отслеживают распределение памяти по исходной строке в рамках отладочных сборников.

Вероятно, есть больше вариантов. Тестирование и использование настраиваемого глобального переопределения new/delete (которое может быть запрошено) должно оказаться полезным, если это позволяет его дизайн.

Кроме того, см. Electronic Arts STL С++ paper для обсуждения того, что нужно сделать в STL/С++ для поддержки правильной разработки игр. (Это, наверное, немного более хардкор, чем ваш движок, но он, безусловно, содержит множество самородков вдохновения и изобретательности.)

Ответ 12

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

Visual Leak Detector для Visual С++ 2008/2010