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

Общие рекомендации по предотвращению утечек памяти в С++

Каковы некоторые общие советы, чтобы убедиться, что я не утечка памяти в программах на С++? Как определить, кто должен освобождать память, которая была динамически распределена?

4b9b3361

Ответ 1

Вместо управления памятью вручную попробуйте использовать интеллектуальные указатели, если это применимо.
Взгляните на Boost lib, TR1 и умные указатели.
Также интеллектуальные указатели теперь являются частью стандарта С++, называемого С++ 11.

Ответ 2

Я полностью поддерживаю все советы относительно RAII и интеллектуальных указателей, но я также хотел бы добавить немного более высокий уровень: самая простая память для управления - это память, которую вы никогда не выделяли. В отличие от таких языков, как С# и Java, где почти все есть ссылка, на С++ вы должны поместить объекты в стек, когда сможете. Как я вижу, некоторые люди (в том числе д-р Stroustrup) отмечают, что основная причина, почему сборка мусора никогда не была популярна на С++, заключается в том, что хорошо написанный С++ не производит много мусора в первую очередь.

Не пишите

Object* x = new Object;

или даже

shared_ptr<Object> x(new Object);

когда вы можете просто написать

Object x;

Ответ 3

Используйте RAII

  • Забудьте о сборке мусора (вместо этого используйте RAII). Обратите внимание, что даже сборщик мусора может также протекать (если вы забудете "null" некоторые ссылки в Java/С#), и этот сборщик мусора не поможет вам распоряжаться ресурсами (если у вас есть объект, который приобрел дескриптор файл, этот файл не будет автоматически высвобождаться, когда объект выходит из области видимости, если вы не выполняете его вручную на Java, или используйте шаблон "dispose" на С#).
  • Забудьте правило "один возврат за функцию" . Это хороший совет C, чтобы избежать утечек, но он устарел на С++ из-за использования исключений (вместо этого используйте RAII).
  • И хотя "Сэндвич-шаблон" является хорошим советом по C, он устарел в С++ из-за использования исключений (вместо этого используйте RAII).

Этот пост, кажется, повторяющийся, но в С++ наиболее простой способ узнать - RAII.

Научитесь использовать интеллектуальные указатели, как от boost, TR1, так и от низкой (но часто достаточно эффективной) auto_ptr (но вы должны знать его ограничения).

RAII является основой безопасности исключений и ресурсов в С++, и никакой другой шаблон (сэндвич и т.д.) не даст вам обоих (и большую часть времени он не даст вам ни одного).

См. ниже сравнение RAII и кода без RAII:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

О RAII

Подводя итог (после комментария от Ogre Psalm33), RAII опирается на три понятия:

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

Это означает, что в правильном коде С++ большинство объектов не будут построены с помощью new и будут объявлены в стеке. А для тех, которые построены с использованием new, все будут как-то областями (например, прикреплены к интеллектуальному указателю).

Как разработчик, это очень эффективно, так как вам не нужно заботиться о ручном управлении ресурсами (как это сделано на C, или для некоторых объектов в Java, которые интенсивно используют try/finally для этого случай)...

Изменить (2012-02-12)

"объекты с областями... будут разрушены... независимо от выхода", что не совсем верно. есть способы обмануть RAII. любой аромат terminate() будет обходить очистку. выход (EXIT_SUCCESS) является оксюмороном в этом отношении.

- wilhelmtell

wilhelmtell совершенно справедливо: есть исключительные способы обмануть RAII, все это приводит к остановке процесса.

Это исключительные способы, потому что код С++ не засоряется с завершением, выходом и т.д., или в случае с исключениями мы хотим необработанные исключение, чтобы скомпрометировать процесс, а ядро ​​сбрасывает его образ памяти, а не после очистки.

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

(который вызывает terminate или exit в случайном С++-коде?... Я помню, что мне пришлось иметь дело с этой проблемой при игре с GLUT: эта библиотека очень C-ориентирована, так как активно ее разрабатывает, чтобы затруднить работу разработчиков С++, не заботясь о стеке выделенные данные или имеющие "интересные" решения о никогда не возвращаются из основного цикла... Я не буду комментировать это).

Ответ 4

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

Вместо

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost:: shared_ptr автоматически удаляется, если счетчик ссылок равен нулю:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

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

Однако это не панацея. Хотя вы можете получить доступ к базовому указателю, вы не захотите передавать его стороннему API, если вы не уверены в его действии. Много раз, ваши "публикации" материала в какой-то другой поток для работы, которая должна быть выполнена ПОСЛЕ завершения области создания. Это часто встречается с PostThreadMessage в Win32:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

Как всегда, используйте мышление с помощью любого инструмента...

Ответ 5

Прочитайте RAII и убедитесь, что вы это поняли.

Ответ 6

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

Первое, что нужно сделать, это выделить в стек каждый раз, когда сможете. Это касается большинства случаев, когда вам нужно выделить один объект для какой-либо цели.

Если вам нужен "новый" объект, то большую часть времени он будет иметь одного очевидного владельца на всю оставшуюся жизнь. Для этой ситуации я обычно использую кучу шаблонов коллекций, которые предназначены для "владения" объектами, хранящимися в них по указателю. Они реализованы с помощью векторных и картографических контейнеров STL, но имеют некоторые отличия:

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

Мой beaf с STL состоит в том, что он настолько сосредоточен на объектах Value, в то время как в большинстве приложений объекты являются уникальными объектами, которые не имеют значимой семантики семантики, необходимой для использования в этих контейнерах.

Ответ 7

Ба, вы, молодые дети, и ваши новомодные сборщики мусора...

Очень сильные правила "собственности" - какой объект или часть программного обеспечения имеет право удалить объект. Четкие комментарии и имена мудрых переменных, чтобы сделать их очевидными, если указатель "владеет" или "просто смотри, не трогай". Чтобы помочь решить, кому принадлежит, следуйте как можно больше шаблону "сэндвич" в каждой подпрограмме или методе.

create a thing
use that thing
destroy that thing

Иногда необходимо создавать и уничтожать в самых разных местах; Я стараюсь избегать этого.

В любой программе, требующей сложных структур данных, я создаю строгое четкое дерево объектов, содержащих другие объекты, с помощью указателей "владелец". Это дерево моделирует базовую иерархию концепций области приложения. Например, 3D-сцена обладает объектами, огнями, текстурами. В конце рендеринга, когда программа завершает работу, есть четкий способ уничтожить все.

Многие другие указатели определяются по мере необходимости, когда один объект нуждается в доступе к другому, для сканирования по arays или что-то еще; это "просто выглядящие". Для примера 3D-сцены - объект использует текстуру, но не принадлежит; другие объекты могут использовать ту же самую текстуру. Уничтожение объекта не вызывает уничтожения любых текстур.

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

Ответ 8

Отличный вопрос!

Если вы используете С++, и вы разрабатываете приложение boud реального времени CPU и памяти (например, игры), вам нужно написать свой собственный диспетчер памяти.

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

  • Распределитель фиксированного размера сильно обсуждается, везде в сети

  • Распределение малых объектов было представлено Александреску в 2001 году в его идеальной книге "Современный дизайн С++"

  • Отличное продвижение (с распространенным исходным кодом) можно найти в удивительной статье в Game Programming Gem 7 (2008) под названием "Высокопроизводительный распределитель кучи", написанной Димитаром Лазаровым

  • Большой список ресурсов можно найти в этой статье

Не начинайте писать noob неиспользуемый распределитель самостоятельно... Сначала ДОКУМЕНТ САЙТ.

Ответ 9

Одним из методов, который стал популярным в управлении памятью на С++, является RAII. В основном вы используете конструкторы/деструкторы для управления распределением ресурсов. Конечно, есть некоторые другие неприятные детали на С++ из-за безопасности исключений, но основная идея довольно проста.

Проблема обычно сводится к одному из прав собственности. Я настоятельно рекомендую прочитать эффективную серию С++ от Scott Meyers и Modern С++ Design от Andrei Alexandrescu.

Ответ 10

Там уже много о том, как не течь, но если вам нужен инструмент, который поможет вам отслеживать утечки, взгляните на:

Ответ 11

Пользовательские умные указатели везде вы можете! Целые классы утечек памяти просто уходят.

Ответ 12

Поделитесь и узнайте правила владения памятью в вашем проекте. Использование правил COM обеспечивает наилучшую согласованность ([in] параметры принадлежат вызывающей стороне, вызываемый должен копировать, [out] params принадлежат вызывающему абоненту, вызываемый должен сделать копию, сохраняя ссылку и т.д.)

Ответ 13

valgrind - хороший инструмент для проверки утечек памяти программ во время выполнения.

Он доступен для большинства вариантов Linux (включая Android) и Darwin.

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

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

Ответ 14

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

Ответ 15

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

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

Это очевидно, но убедитесь, что вы вводите его перед тем, как вводить код в области

Ответ 16

Частый источник этих ошибок - это когда у вас есть метод, который принимает ссылку или указатель на объект, но оставляет право собственности неясным. Соглашения о стиле и комментариях могут сделать это менее вероятным.

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

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

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

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

Ответ 17

Советы в порядке важности:

-Tip # 1 Всегда помните, чтобы объявить деструкторы "виртуальными".

-Tip # 2 Используйте RAII

-Tip # 3 Используйте интеллектуальные указатели boost

-Tip # 4 Не пишите свои собственные глючные Smartpointers, используйте boost (в проекте, в котором я сейчас, я не могу использовать boost, и мне пришлось отлаживать свои собственные умные указатели, я бы определенно не повторять тот же маршрут снова, но опять же прямо сейчас я не могу добавить boost к нашим зависимостям)

-Tip # 5 Если его некоторая случайная/неэффективная критическая (как и в играх с тысячами объектов) работа над Thorsten Ottosen boost pointer container

-Tip # 6 Найдите заголовок обнаружения утечки для вашей платформы выбора, например, заголовок "vld" обнаружения утечки изображения

Ответ 18

Если вы можете, используйте boost shared_ptr и стандартный С++ auto_ptr. Они передают семантику собственности.

Когда вы возвращаете auto_ptr, вы говорите вызывающему, что вы даете им право собственности на память.

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

Эти семантики также применяются к параметрам. Если вызывающий абонент передает вам auto_ptr, они дают вам право собственности.

Ответ 19

Если вы собираетесь управлять своей памятью вручную, у вас есть два случая:

  • Я создал объект (возможно, косвенно, вызывая функцию, которая выделяет новый объект), я использую его (или использую функцию, которую я вызываю), затем я освобождаю его.
  • Кто-то дал мне ссылку, поэтому я не должен ее освобождать.

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

Это все о собственности на указатель.

Ответ 20

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

Valgrind memcheck - отличный бесплатный.

Ответ 21

Только для MSVC добавьте следующее в начало каждого файла .cpp:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

Затем, при отладке с VS2003 или выше, вам будет рассказано о любых утечках, когда ваша программа выйдет (она отслеживает новое/удаление). Это основное, но это помогло мне в прошлом.

Ответ 22

valgrind (только для платформ * nix) - очень хорошая проверка памяти

Ответ 23

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

Это также можно сделать во время компиляции, заменив операторы new и delete и другие функции выделения памяти.

Например, проверьте этот сайт [Отладка выделения памяти на С++] Примечание. Существует трюк для оператора delete также примерно так:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

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

Вы также можете попробовать что-то вроде BoundsChecker в Visual Studio, который довольно интересен и прост в использовании.

Ответ 24

Мы переносим все наши функции распределения слоем, который добавляет краткую строку спереди и флаг сторожевой части в конце. Так, например, у вас будет вызов "myalloc (pszSomeString, iSize, iAlignment) или новый (" описание ", iSize) MyObject(), который внутренне выделяет указанный размер плюс достаточное пространство для вашего заголовка и часового пояса., не забудьте прокомментировать это для не-отладочных сборников! Для этого требуется немного больше памяти, но преимущества намного перевешивают затраты.

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

Ответ 25

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

Ответ 26

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

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

Здесь вместо этого функция потока

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

Довольно easyn не так ли? В случае сбоя создания потока ресурс будет свободен (удален) с помощью auto_ptr, в противном случае право собственности будет передано в поток. Что делать, если поток работает так быстро, что после создания он освобождает ресурс до

param.release();

вызывается в основной функции/методе? Ничего! Потому что мы будем говорить "auto_ptr", чтобы игнорировать освобождение. Легко ли управление памятью С++ не так ли? Cheers,

Эма!

Ответ 27

Управляйте память так же, как вы управляете другими ресурсами (ручками, файлами, соединениями db, сокетами...). GC не поможет вам и с ними.

Ответ 28

  • Старайтесь не выделять объекты динамически. Пока классы имеют соответствующие конструкторы и деструкторы, используйте переменную типа класса, а не указатель на нее, и вы избегаете динамического распределения и освобождения, потому что компилятор сделает это за вас.
    Фактически это также механизм, используемый "умными указателями" и называемый RAII некоторыми из других авторов;-).
  • Когда вы передаете объекты другим функциям, предпочитайте ссылочные параметры по указателям. Это позволяет избежать некоторых возможных ошибок.
  • Объявлять параметры const, где это возможно, особенно указатели на объекты. Таким образом, объекты не могут быть освобождены "случайно" (за исключением случаев, когда вы удаляете const;-))).
  • Сведите к минимуму количество мест в программе, где вы занимаетесь распределением памяти и освобождением памяти. E. g. если вы выделяете или освобождаете один и тот же тип несколько раз, напишите для него функцию (или метод factory;-)).
    Таким образом, вы можете создать отладочный вывод (какие адреса выделяются и освобождаются,...), если это необходимо.
  • Используйте функцию factory для выделения объектов нескольких связанных классов из одной функции.
  • Если ваши классы имеют общий базовый класс с виртуальным деструктором, вы можете освободить все из них, используя одну и ту же функцию (или статический метод).
  • Проверьте свою программу с помощью инструментов, таких как очистка (к сожалению, много $/€/...).

Ответ 29

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

Слишком легко сделать ошибку иначе:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.