Как защититься от утечек памяти? - PullRequest
10 голосов
/ 02 февраля 2010

Недавно я проходил собеседование на должность в C ++, и меня спросили, как я защищаюсь от утечек памяти. Я знаю, что не дал удовлетворительного ответа на этот вопрос, поэтому я бросаю его вам, ребята. Каковы наилучшие способы защиты от утечек памяти?

Спасибо!

Ответы [ 14 ]

20 голосов
/ 02 февраля 2010
  1. Не выделяйте память в куче, если вам это не нужно. Большая часть работы может выполняться в стеке, поэтому выделять кучу памяти следует только тогда, когда это абсолютно необходимо.

  2. Если вам нужен выделенный из кучи объект, принадлежащий одному другому объекту, используйте std::auto_ptr.

  3. Используйте стандартные контейнеры или контейнеры из Boost вместо того, чтобы придумывать свои собственные.

  4. Если у вас есть объект, на который ссылаются несколько других объектов, и который не принадлежит ни одному из них, используйте либо std::tr1::shared_ptr, либо std::tr1::weak_ptr - в зависимости от того, что подходит вашему варианту использования. 1019 *

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

20 голосов
/ 02 февраля 2010

Все ответы на вопросы сводятся к следующему: избегайте необходимости звонить delete.

Каждый раз, когда программист должен вызвать delete, у вас есть потенциальная утечка памяти. Вместо этого вызов delete происходит автоматически. C ++ гарантирует, что локальные объекты будут вызывать свои деструкторы, когда они выходят из области видимости. Используйте эту гарантию, чтобы гарантировать, что ваши выделения памяти будут автоматически удалены.

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

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

Конечно, в зависимости от использования может потребоваться другая семантика. Вам нужен простой случай, когда распределение должно длиться ровно столько, сколько длится класс-обертка? Затем используйте boost::scoped_ptr или, если вы не можете использовать повышение, std::auto_ptr. У вас есть неизвестное количество объектов, ссылающихся на распределение, не зная, как долго будет жить каждый из них? Тогда подсчет ссылок boost::shared_ptr является хорошим решением.

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

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

Но их всех объединяет ответ на ваш вопрос: идиома RAII : получение ресурсов - инициализация. Выделение памяти является своего рода ресурсом. Ресурсы следует извлекать при инициализации объекта и освобождать его самим, когда он уничтожается.

Заставьте C ++ и правила жизни сделать свою работу за вас. Никогда не вызывайте delete вне объекта RAII, будь то контейнерный класс, умный указатель или какая-то специальная оболочка для отдельного размещения. Пусть объект обрабатывает назначенный ему ресурс.

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

8 голосов
/ 02 февраля 2010

Вы бы хорошо прочитали RAII .

7 голосов
/ 02 февраля 2010

заменить новое на shared_ptr. В основном RAII. сделать исключение кода безопасным. Используйте STL везде, где это возможно. Если вы используете указатели подсчета ссылок, убедитесь, что они не образуют циклы. SCOPED_EXIT от boost также очень полезен.

3 голосов
/ 02 февраля 2010
  1. ( Easy ) Никогда не позволяйте необработанному указателю владеть объектом (найдите в вашем коде регулярное выражение "\= *new". Используйте shared_ptr или scoped_ptr вместо этого, или даже лучше, используйте реальные переменные вместо указателей так часто, как можете.

  2. ( Hard ) Убедитесь, что у вас нет циклических ссылок, так как shared_ptrs указывают друг на друга, используйте weak_ptr , чтобы разбить их.

Готово!

2 голосов
/ 02 февраля 2010

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

C ++ позволяет создавать объекты в стеке (то есть в виде локальных переменных). Это связывает создание и уничтожение потока управления: объект создается, когда выполнение программы достигает своего объявления, и объект уничтожается, когда выполнение выходит из блока, в котором было сделано это объявление. Когда распределение соответствует этому шаблону, используйте его. Это избавит вас от многих неприятностей.

Для других случаев, если вы можете определить и документ с четким понятием ответственности, тогда это может работать нормально. Например, у вас есть метод или функция, которая возвращает указатель на вновь выделенный объект, и вы документально подтверждаете, что вызывающая сторона становится ответственной за окончательное удаление этого экземпляра. Четкая документация в сочетании с хорошей дисциплиной программиста (что нелегко достичь!) Могут решить многие оставшиеся проблемы управления памятью.

В некоторых ситуациях, включая недисциплинированных программистов и сложные структуры данных, вам, возможно, придется прибегнуть к более продвинутым методам, таким как подсчет ссылок. Каждому объекту присваивается «счетчик», который представляет собой число других переменных, которые указывают на него. Всякий раз, когда часть кода решает больше не указывать на объект, счетчик уменьшается. Когда счетчик достигает нуля, объект удаляется. Подсчет ссылок требует строгой обработки счетчиков. Это можно сделать с помощью так называемых «умных указателей»: это объекты, которые являются функционально указателями, но автоматически настраивают счетчик на свое собственное создание и уничтожение.

Подсчет ссылок работает довольно хорошо во многих ситуациях, но они не могут обрабатывать циклические структуры. Поэтому для самых сложных ситуаций вам придется прибегнуть к тяжелой артиллерии, то есть к сборщику мусора . Я ссылаюсь на GC для C и C ++, написанный Хансом Бёмом, и он использовался в некоторых довольно крупных проектах (например, Inkscape ). Задача сборщика мусора состоит в том, чтобы поддерживать глобальный взгляд на все пространство памяти, чтобы знать, используется ли данный экземпляр по-прежнему или нет. Это правильный инструмент, когда инструментов локального просмотра, таких как подсчет ссылок, недостаточно. Можно утверждать, что в этот момент следует спросить себя, является ли C ++ правильным языком для рассматриваемой проблемы. Сборка мусора работает лучше всего, когда язык совместный (это открывает множество оптимизаций, которые невозможно выполнить, когда компилятор не знает, что происходит с памятью, как типичный компилятор C или C ++).

Обратите внимание, что ни один из методов, описанных выше, не позволяет программисту перестать думать. Даже GC может страдать от утечек памяти, потому что он использует достижимость как приближение будущее использование (есть теоретические причины, которые подразумевают, что, в общем смысле, невозможно обнаружить все объекты, которые впоследствии не будут использоваться). Возможно, вам все равно придется установить для некоторых полей значение NULL, чтобы сообщить GC, что вы больше не будете обращаться к объекту через данную переменную.

2 голосов
/ 02 февраля 2010

В дополнение к совету о RAII, не забудьте сделать деструктор базового класса виртуальным, если есть какие-либо виртуальные функции.

2 голосов
/ 02 февраля 2010
  • убедитесь, что вы точно понимаете, как объект будет удаляться при каждом его создании
  • убедитесь, что вы понимаете, кому принадлежит указатель, каждый раз, когда он вам возвращается
  • убедитесь, что ваши пути ошибок располагают правильно созданными объектами
  • быть параноиком по поводу вышеупомянутого
2 голосов
/ 02 февраля 2010

Используйте все виды умных указателей.

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

1 голос
/ 02 декабря 2011

Вы можете использовать утилиту. Если вы работаете в Linux - используйте valgrid (это бесплатно). Используйте deleteaker в Windows.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...