Как отслеживать распределение памяти в C ++ (особенно new / delete) - PullRequest
28 голосов
/ 13 января 2009

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

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

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

Ответы [ 16 ]

26 голосов
/ 13 января 2009

Я бы порекомендовал вам использовать valgrind для Linux. Он будет ловить не освобожденную память, среди других ошибок, таких как запись в нераспределенную память. Другим вариантом является mudflap, который также сообщает вам о неосвобожденной памяти. Используйте опции -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. Так что, если вы хотите их использовать, поместите их объявление в заголовок и включите его во все файлы, которые нужно просмотреть.

24 голосов
/ 25 февраля 2011

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

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

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

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

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

Удачи!

9 голосов
/ 13 января 2009

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

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

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

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" в каждом исходном файле, в котором вам нужно отслеживать память (вероятно, все они). Поскольку в заголовочном файле нет определений, вы не получите дубликаты во время ссылки, а поскольку объявления есть, вы также не получите неопределенные ссылки.

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

7 голосов
/ 13 января 2009

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

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

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

3 голосов
/ 15 марта 2010

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

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

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

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

3 голосов
/ 13 января 2009

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

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

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

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

3 голосов
/ 13 января 2009

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

Цитировать сайт:

Это очень эффективный способ быстро диагностировать и устранять утечки памяти в Приложения на C / C ++.

http://dmoulding.googlepages.com/vld

1 голос
/ 13 января 2009

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

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

1 голос
/ 13 января 2009
0 голосов
/ 26 февраля 2018

Проверьте этот крошечный удобный код, теперь вместо 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...