it-swarm-ru.tech

Общие рекомендации, чтобы избежать утечек памяти в C ++

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

124
dulipishi

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

37
Andri Möll

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

Не пиши

Object* x = new Object;

или даже

shared_ptr<Object> x(new Object);

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

Object x;
194
Ross Smith

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

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

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

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

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

Смотрите ниже сравнение 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

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

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

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

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

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

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

- wilhelmtell

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

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

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

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

100
paercebal

Вы захотите взглянуть на умные указатели, такие как умные указатели повышения .

Вместо

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!
}

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

25
Doug T.

Читайте RAII и убедитесь, что вы понимаете это.

12
Hank

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

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

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

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

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

11
Jeroen Dirks

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

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

create a thing
use that thing
destroy that thing

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

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

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

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

10
DarenW

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

если вы используете c ++ и разрабатываете приложение для работы с процессором и памятью в реальном времени (например, игры), вам нужно написать собственный менеджер памяти.

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

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

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

  • Большое продвижение (с распределенным исходным кодом) можно найти в удивительной статье в Game Programming Gem 7 (2008) под названием "High Performance Heap Allocator", написанной Димитром Лазаровым.

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

Не начинайте писать ненужный распределитель noob самостоятельно ... ДОКУМЕНТИТЕ СЕБЕ в первую очередь.

8
ugasoft

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

Проблема, как правило, сводится к собственности. Я настоятельно рекомендую прочитать серию Effective C++ Скотта Мейерса и Modern C++ Design Андрея Александреску.

5
Jason Dagit

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

5
fabiopedrosa

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

4
DougN

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

4
Seth Morris

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

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

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

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

4
Joseph

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

3
Joseph

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

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

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

2
Seth Morris

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

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

-Совет № 2 Используйте RAII

-Совет №3 Используйте умные указатели Boost

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

-Совет №5. Если его случайное/не критичное к производительности (как в играх с тысячами объектов) работа, посмотрите на контейнер повышения указателя Торстена Оттосена

Подсказка # 6 Найдите заголовок обнаружения утечки для выбранной вами платформы, такой как заголовок vld для Visual Leak Detection

2
Robert Gould

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

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

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

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

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

2
Jonathan

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

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

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

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

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

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

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

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

Это все о владении указателем.

1
Null303

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

1
Ronny Brendel

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

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

1
eli

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

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

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

1
Rob

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

0
artificialidiot

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

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;
}

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

param.release();

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

Эма!

0
Emanuele Oriani

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

0
Nemanja Trifunovic

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

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

Например, проверьте это site [Отладка выделения памяти в C++] Примечание: есть оператор для удаления, также что-то вроде этого:

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

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

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

0
INS

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

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

0
screenglow