Как отследить распределение памяти в C++ (особенно new/delete)
Как я могу отслеживать распределение памяти в C++, особенно те, которые сделаны new
/delete
, Для объекта я могу легко переопределить operator new
, но я не уверен, как глобально переопределить все выделения, чтобы они проходили через мой обычай new
/delete
, Это не должно быть большой проблемой, но я не уверен, как это должно быть сделано (#define new MY_NEW
?).
Как только это сработает, я предполагаю, что достаточно иметь где-то карту указателя / местоположения выделения, чтобы я мог отслеживать все выделения, которые в данный момент являются "активными", и - в конце приложения - проверять выделения которые не были освобождены.
Ну, это снова похоже на то, что, конечно, было сделано, по крайней мере, несколько раз, так есть ли какая-нибудь хорошая библиотека (желательно портативная)?
17 ответов
Я бы порекомендовал вам использовать 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
освободить память.
Также обратите внимание, что, как указал Пакс и для своего решения, это будет показывать только утечки, вызванные кодом, использующим наш собственный определенный оператор new/delete. Так что, если вы хотите использовать их, поместите их объявление в заголовок и включите его во все файлы, которые должны просматриваться.
Чтобы быть конкретным, используйте инструмент массива 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.
Удачи!
Вы можете использовать код по адресу 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"
в каждом исходном файле, в котором вам нужно отслеживать память (вероятно, все они). Поскольку в заголовочном файле нет определений, вы не получите дубликаты во время ссылки, а поскольку объявления есть, вы также не получите неопределенные ссылки.
Имейте в виду, что это не будет отслеживать утечки памяти в коде, который вы не компилируете (например, сторонние библиотеки), но он должен сообщать вам о ваших собственных проблемах.
Что ж, вы можете повторно реализовать глобальные операторы new и delete, чтобы предоставить вам необходимую функциональность, но я бы не советовал этого делать, если только это не единственный способ отслеживать распределение памяти, например, из-за ограничений вашей платформы.
Отладчики памяти доступны для большинства распространенных платформ разработки. Взгляните на PurifyPlus для коммерческого решения, которое работает в Windows и различных Unix-системах, или valgrind для открытого исходного кода, который работает в Linux (и, возможно, в других операционных системах, но я когда-либо использовал его только в Linux).
Если вы намереваетесь заменить глобальные операторы, ознакомьтесь с этой статьей.
Для наших проектов на платформе Windows C++ я использую VLD, Visual Leak Detector, который почти слишком легко реализовать, который отслеживает и сообщает об утечках памяти при выходе из вашего приложения - лучше всего, он бесплатный и источник доступен. Систему можно настроить для создания отчетов несколькими способами (регистратор дисков, IDE, XML и т. Д.), И она неоценима для обнаружения утечек в службах Windows, которые всегда являются проблемой для отладки. Так что, пока вы ищете портативное решение, если вы хотите накатить свое собственное, вы, конечно, можете просмотреть источник руководства. Надеюсь, поможет.
Цитировать сайт:
Это очень эффективный способ быстрой диагностики и устранения утечек памяти в приложениях C/C++.
В Linux есть как минимум два традиционных метода:
- malloc () и free() (и другие связанные с памятью функции) являются слабыми символами, что означает, что вы можете просто переопределить их, и ваши версии будут использованы. Пример реализации: см. Электрический забор.
- С помощью переменной среды LD_PRELOAD вы можете переопределить символы (как слабые, так и сильные) в общих библиотеках символами, найденными в библиотеках, содержащихся в переменной среды LD_PRELOAD. Если вы скомпилировали разделяемую библиотеку с помощью malloc(), free() и friends, все готово. Опять же, электрический забор демонстрирует это.
Таким образом, вы не только перехватываете новые и удаляете, но и выполняете функции выделения памяти в стиле C. Я еще не делал этого на Windows, но я видел способы переписать, как библиотеки DLL там тоже связаны (хотя я помню, что они были немного неуклюжими).
Однако обратите внимание, что помимо того, что это интересные методы, я бы рекомендовал использовать valgrind, чтобы делать то, что вы хотите, выше всего остального.
Если вы разрабатываете под Windows, бесплатный инструмент DebugDiag поможет найти память и устранить утечки.
Вам не нужно улучшать вашу программу, чтобы DebugDiag работал.
Хотя это не самая простая и интуитивно понятная программа для использования! Убедитесь, что вы в Google для учебников и инструкций о том, как его использовать.
Не отвечая непосредственно на ваш вопрос, но если вы действительно хотите получить список пропущенных объектов кучи в конце программы, вы можете просто запустить программу с помощью valgrind.
Для MS VS вы можете поиграть с Debug CRT Heap. Не так просто, как valgrind, здесь слишком много объяснений, но вы можете делать то, что хотите.
Если мне нужен инструмент, я обычно начинаю с того, что предоставляет мой компилятор / стандартная библиотека.
- Если вы используете glibc, вы можете использовать mtrace. Он устанавливает глобальный хук, который регистрирует каждую функцию выделения памяти glibc (malloc, realloc, memalign, free и все, что реализовано поверх них, как new/delete)
- Если вы используете Microsoft CRT, вы можете взглянуть на CRT Debug Heap Details. Есть примеры, как установить отладочную версию функций выделения памяти, получить статистику кучи, найти утечки памяти и т. Д.
Я заметил, что многие другие ответы сосредоточены на том, какие инструменты вы можете использовать. Я использовал некоторые из них, и они очень помогают.
Но, как упражнение по программированию, и, увидев, что вы работаете с C++, вам нужно будет переопределить глобальные new и delete, а также malloc, free и realloc. Вы могли бы подумать, что будет достаточно только переопределить new и delete, но std::string и другие классы, вероятно, будут использовать malloc и особенно realloc.
Затем, как только вы это сделаете, вы можете начать добавлять заголовки, чтобы проверять перезапись памяти, отслеживать записи стека для каждого выделения и так далее.
В общем, я бы порекомендовал вам использовать один из инструментов, упомянутых здесь, но было бы интересно написать свою собственную систему.
Если вы разрабатываете под Linux, одним из лучших инструментов для этого (например, обнаружение утечек памяти, отслеживание выделений в определенных местах кода) является valgrind, особенно его инструмент массива. Единственным недостатком является то, что программа работает медленнее (или намного медленнее), поэтому она полезна только для отладки.
Вы можете использовать добавить файл заголовка ( MemTracker.h), указанный в этой ссылке, к вашему решению, чтобы отслеживать распределение / освобождение памяти в C и C++. Он показывает, есть ли у вас утечка памяти и какая строка кода отвечает за это.
#include<iostream>
void * operator new(size_t size)
{
std::cout<<"Allocating:"<<size<<std::endl;
return malloc (size);
}
void operator delete(void *ptr)
{
std::cout<<"Deleting:"<<ptr<<std::endl;
free(ptr);
}
int main() {
std::string ss("1234567890123456");
}
Если вы не видите вызываемых перегруженных операторов (yo может быть на другом компиляторе, а не на моем g++), попробуйте увеличить длину строки.
Это недешево, но в свое время на С ++ я обнаружил, что очистка - лучший инструмент для отладки утечек и других проблем с памятью (то же самое, что теперь принадлежит IBM, поэтому surport пошла в гору). Bounds Checker понравился некоторым людям, но он плохо работал с программным обеспечением, которое я разрабатывал.
Если вы хотите сделать это как упражнение по программированию, это может дать вам гораздо больше понимания, чтобы вместо этого написать свой собственный класс (ы) интеллектуальных указателей и последовательно использовать их в этом проекте (или модуле проекта).
Проверьте этот крошечный удобный код, теперь вместо 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;
}