Как отслеживать выделение памяти в C++ (особенно новое / удаление)

Как я могу отслеживать выделение памяти в C++, особенно те, которые выполняются new / delete. Для объекта я могу легко переопределить operator new, но я не уверен, как глобально переопределить все выделения, чтобы они проходили через мои собственные new / delete. Это не должно быть большой проблемой, но я не уверен, как это должно быть сделано (#define new MY_NEW?).

Как только это сработает, я бы предположил, что достаточно иметь карту где-нибудь с указателем / местоположением выделения, чтобы я мог отслеживать все выделения, которые в настоящее время являются `` активными '', и - в конце приложения - проверять выделения которые не были освобождены.

Что ж, похоже, что это снова похоже на то, что наверняка было сделано по крайней мере несколько раз, так что какая-нибудь хорошая библиотека (желательно портативная)?

Нет универсального готового ответа. Пожалуйста, предоставьте дополнительную информацию об используемой ОС и платформе.

kauppi 13.01.2009 13:59

Мне понадобится решение, которое работает как минимум как в Linux, так и в Windows, а также, желательно, в Mac OS.

Anteru 13.01.2009 15:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
36
2
35 749
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

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

Для MS VS вы можете играть с куча отладки CRT. Не так просто, как valgrind, здесь слишком много, чтобы объяснять, но может делать то, что вы хотите.

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

Anteru 13.01.2009 13:24

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

Отладчики памяти доступны для большинства распространенных платформ разработки. Взгляните на PurifyPlus для коммерческого решения, которое работает в Windows и различных Unix, или Valgrind для решения с открытым исходным кодом, которое работает в Linux (и, возможно, в других операционных системах, но я когда-либо использовал его только в Linux).

Если вы намереваетесь заменить глобальные операторы, взгляните на эта статья.

Вы можете использовать код http://www.flipcode.com/archives/How_To_Find_Memory_Leaks.shtml со следующими модификациями: приведенный код работает только в том случае, если у вас есть один большой исходный файл honkin. Я разобрался с этим для другого вопроса по SO (здесь).

Для начала, не измените stdafx.h, внесите изменения в свои собственные файлы.

Создайте отдельный файл заголовка mymemory.h и поместите в него, например, прототипы функций (обратите внимание, что в нем нет тело):

inline void * __cdecl operator new(unsigned int size,
    const char *file, int line);

Также в этот заголовок поместите другие прототипы для AddTrack (), DumpUnfreed () и т. д., А также #defines, typedef и оператор extern:

extern AllocList *allocList;

Затем в новом mymemory.cpp (который также # включает mymemory.h) поместите фактическое определение allocList вместе со всеми реальными функциями (а не только с прототипами) и добавьте этот файл в свой проект.

Затем #include "mymemory.h" в каждом исходном файле, в котором вам нужно отслеживать память (возможно, во всех). Поскольку в файле заголовка нет определений, вы не получите дубликатов во время ссылки, а поскольку объявления есть, вы также не получите неопределенные ссылки.

Имейте в виду, что это не будет отслеживать утечки памяти в коде, который вы не компилируете (например, сторонние библиотеки), но должно сообщить вам о ваших собственных проблемах.

Для наших проектов C++ на платформе Windows я использую VLD, Visual Leak Detector, который почти слишком легко реализовать, он отслеживает и сообщает об утечках памяти при выходе из вашего приложения - лучше всего его бесплатно и исходный код доступен. Систему можно настроить для создания отчетов несколькими способами (регистратор дисков, IDE, XML и т. д.), И она оказалась бесценной для обнаружения утечек в службах Windows, которые всегда сложно отладить. Итак, пока вы ищете портативное решение, если вы хотите использовать свое собственное, вы, конечно, можете просмотреть исходный код в качестве руководства. Надеюсь, поможет.

Чтобы процитировать сайт:

It's a very effective way to quickly diagnose, and fix, memory leaks in C/C++ applications.

http://dmoulding.googlepages.com/vld

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

В Linux есть как минимум два традиционных метода:

  • malloc () и free () (и другие функции, связанные с памятью) являются слабыми символами, что означает, что вы можете просто переопределить их, и ваши версии будут использоваться. Пример реализации: см. Электрический забор.
  • С помощью переменной среды LD_PRELOAD вы можете переопределить символы (как слабые, так и сильные) в общих библиотеках символами из библиотек, содержащихся в переменной среды LD_PRELOAD. Если вы скомпилируете общую библиотеку с помощью malloc (), free () и друзей, все готово. Опять же, электрический забор демонстрирует это.

Таким образом, вы не только перехватываете новые и удаляете, но также и функции выделения памяти в стиле C. Я еще не делал этого в Windows, но я видел методы, позволяющие переписать то, как библиотеки DLL связаны и там (хотя я помню, что они были довольно неуклюжими).

Однако обратите внимание, что помимо того факта, что это интересные методы, я бы рекомендовал использовать valgrind, чтобы делать то, что вы хотите, прежде всего.

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

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

Но в качестве упражнения по программированию и, увидев, что вы работаете с C++, вам нужно будет переопределить глобальные команды new и delete, а также malloc, free и realloc. Вы могли подумать, что переопределения только new и delete будет достаточно, но std :: string и другие классы, скорее всего, будут использовать malloc и особенно realloc.

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

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

Я серьезно сомневаюсь, что строка std:; будет использовать realloc, поскольку она должна использовать предоставленный распределитель, который не поддерживает realloc.

Anteru 13.01.2009 15:43
Ответ принят как подходящий

Я бы порекомендовал вам использовать valgrind для Linux. Помимо других ошибок, таких как запись в нераспределенную память, он будет обнаруживать неосвобожденную память. Другой вариант - брызговик, который тоже сообщает о неосвободившейся памяти. Используйте опции -fmudflap -lmudflap с gcc, затем запустите вашу программу с MUDFLAP_OPTIONS=-print-leaks ./my_program.

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

template<typename T>
struct track_alloc : std::allocator<T> {
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::size_type size_type;

    template<typename U>
    struct rebind {
        typedef track_alloc<U> other;
    };

    track_alloc() {}

    template<typename U>
    track_alloc(track_alloc<U> const& u)
        :std::allocator<T>(u) {}

    pointer allocate(size_type size, 
                     std::allocator<void>::const_pointer = 0) {
        void * p = std::malloc(size * sizeof(T));
        if (p == 0) {
            throw std::bad_alloc();
        }
        return static_cast<pointer>(p);
    }

    void deallocate(pointer p, size_type) {
        std::free(p);
    }
};

typedef std::map< void*, std::size_t, std::less<void*>, 
                  track_alloc< std::pair<void* const, std::size_t> > > track_type;

struct track_printer {
    track_type * track;
    track_printer(track_type * track):track(track) {}
    ~track_printer() {
        track_type::const_iterator it = track->begin();
        while(it != track->end()) {
            std::cerr << "TRACK: leaked at " << it->first << ", "
                      << it->second << " bytes\n";
            ++it;
        }
    }
};

track_type * get_map() {
    // don't use normal new to avoid infinite recursion.
    static track_type * track = new (std::malloc(sizeof *track)) 
        track_type;
    static track_printer printer(track);
    return track;
}

void * operator new(std::size_t size) throw(std::bad_alloc) {
    // we are required to return non-null
    void * mem = std::malloc(size == 0 ? 1 : size);
    if (mem == 0) {
        throw std::bad_alloc();
    }
    (*get_map())[mem] = size;
    return mem;
}

void operator delete(void * mem) throw() {
    if (get_map()->erase(mem) == 0) {
        // this indicates a serious bug
        std::cerr << "bug: memory at " 
                  << mem << " wasn't allocated by us\n";
    }
    std::free(mem);
}

int main() {
    std::string *s = new std::string;
        // will print something like: TRACK: leaked at 0x9564008, 4 bytes
}

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

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

Также обратите внимание, как Pax также указал для своего решения, это будет показывать только утечки, вызванные кодом, использующим наш собственный определенный оператор new / delete. Поэтому, если вы хотите их использовать, поместите их объявление в заголовок и включите его во все файлы, за которыми следует следить.

Отличный пост. Мне очень помог ваш пример отследить и исправить утечку памяти во встраиваемом устройстве :)

tkarls 16.08.2012 12:53

Хороший пример! Следует отметить, что этот код не является потокобезопасным, поэтому в многопоточной среде (где new и delete будут вызываться из нескольких потоков) вам придется защитить доступ к карте track с помощью std::mutex.

gbmhunter 13.10.2017 22:20

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

Вам не нужно расширять свою программу, чтобы DebugDiag работал.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28BD5941-C458-46F1-B24D-F60151D875A3&displaylang=en

Хотя это не самая простая и интуитивно понятная программа! Обязательно найдите в Google учебные пособия и инструкции по его использованию.

Чтобы быть конкретным, используйте инструмент массивов valgrind. В отличие от memcheck, massif не занимается незаконным использованием памяти, а отслеживает распределение во времени. Он хорошо справляется с «эффективным» измерением использования памяти кучи программой. Самое приятное то, что вам не нужно писать код. Пытаться:

http://valgrind.org/docs/manual/ms-manual.html

Или, если вы очень нетерпеливы:

valgrind --tool=massif <executable> <args>
ms_print massif.out.<pid> | less

Это даст вам график распределения во времени и обратную трассировку того, где произошли большие распределения. Этот инструмент лучше всего запускать в Linux, я не знаю, есть ли вариант для Windows. делает работает на OS X.

Удачи!

Отличный, еще один инструмент valgrind, о котором я не знал!

BenC 18.09.2014 16:33

Это недешево, но в дни, когда я работал на C++, я обнаруживал, что очищать - лучший инструмент для отладки утечек и других проблем с памятью (то же самое, что сейчас принадлежит IBM, так что Surport пошел вниз). Проверка границ нравился некоторым людям, но не подходил для разрабатываемого мной программного обеспечения.

Вы можете использовать файл заголовка (MemTracker.h), указанный в этом ссылка на сайт, в свое решение для отслеживания выделения / освобождения памяти в C и C++. Он показывает, есть ли у вас утечка памяти и какая строка кода отвечает за нее.

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

  • Если вы используете glibc, вы можете использовать mtrace. Он устанавливает глобальную ловушку, которая регистрирует каждую функцию выделения памяти glibc (malloc, realloc, memalign, free и все, что реализовано поверх них, например new / delete)
  • Если вы используете Microsoft CRT, вы можете посмотреть Сведения о куче отладки CRT. Приведены примеры установки отладочной версии функций распределения памяти, получения статистики кучи, поиска утечек памяти и т. д.

Проверьте этот крошечный удобный код, теперь вместо new используйте NEW и отслеживайте все выделения в конструкторе NewHelper:

#include <iostream>

class NewHelper
{
   private :
    void* addr = nullptr;
       public :
       NewHelper(void * addr_)
       {
          addr = addr_;
          std::cout<<addr<<std::endl;
       }
       template <class T>
       operator T ()
       {
           return (T)addr;
       }
};
#define NEW (NewHelper)(void*)new
int main()
{
  int * i = NEW int(0);
 return 0;
}

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

Lightness Races in Orbit 26.02.2018 19:39

компилятор будет преобразовывать тип с помощью NewHelper :: operator T, в любом случае я закодировал трассировщик памяти fullc / C++, который отслеживает каждое выделение в ваших файлах и файлах stdlib ...., я могу продать его, если кому интересно, функции: - Журнал трассировки стека для всех распределений, которые никогда не освобождаются: - Журнал трассировки стека для всех выделений, свободных более одного раза, -Stacktrace для распределений, недействительных free () ... - отображение трассировки стека для всех распределений, произошедших в конструкторе атрибутов, когда родительский объект выделен, но никогда удалено (конструктор не вызывается)

Dhia Hassen 27.02.2018 16:50

Другие вопросы по теме