Справочник функций

Ваш аккаунт

Логин:
Пароль:
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:
Убрать блок ВКонтакте навсегда

Последние темы форума

Показать новые сообщения »

Почтовая рассылка



Подписчиков: 19017
Последний выпуск: 23.04.2012

Как обнаружить утечку памяти

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

Как обнаружить утечку памяти

Введение

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

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

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

Реализация

Проект написан на Visual C++, но переписать его на любой другой диалект С++ не будет слишком сложной задачей. Во-первых, нужно переопределить стандартные операторы new и delete так, чтобы это работало во всех проектах. Поэтому в stdafx.h добавляем следующий фрагмент:

#ifdef _DEBUG
inline void * __cdecl operator new(unsigned int size, 
                                   const char *file, int line)
{
};

inline void __cdecl operator delete(void *p)
{
};
#endif

Как видите, переопределение операторов происходит в блоке #ifdef/#endif. Это ограждает наш код от влияния на релиз компилируемой программы. Вы, наверное, заметили, что теперь оператор new имеет три параметра вместо одного. Два дополнительных параметра содержат имя файла и номер строки, в которой выделяется память. Это удобно для обнаружения конкретного места, где происходит ошибка. Однако код наших проектов по-прежнему ссылается на оператор new, принимающий один параметр. Для исправления этого несоответствия нужно добавиить следующий фрагмент

#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW

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

#ifdef _DEBUG
	inline void * __cdecl operator new(unsigned int size,
	                                   const char *file, int line) {
		void *ptr = (void *)malloc(size);
		AddTrack((DWORD)ptr, size, file, line);
		return(ptr);
		};
	inline void __cdecl operator delete(void *p) {
		RemoveTrack((DWORD)p);	
		free(p);
		};
#endif

Для полноты картины нужно переопределить операторы new[] и delete[], однако никаких существенных отличий здесь нет - творите!

Последний штрих - пишем функции AddTrack() и RemoveTrack(). Для создания списка используемых блоков памяти будем использовать стандартные средства STL:

typedef struct {
    DWORD address;
    DWORD	size;
    char	file[64];
    DWORD	line;
} ALLOC_INFO;

typedef list AllocList;

AllocList *allocList;

void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum)
{
    ALLOC_INFO *info;

    if(!allocList) {
	      allocList = new(AllocList);
    }

    info = new(ALLOC_INFO);
    info->address = addr;
    strncpy(info->file, fname, 63);
    info->line = lnum;
    info->size = asize;
    allocList->insert(allocList->begin(), info);
};

void RemoveTrack(DWORD addr)
{
    AllocList::iterator i;

    if(!allocList)
	      return;
    for(i = allocList->begin(); i != allocList->end(); i++)
    {
	      if((*i)->address == addr)
	      {
		      allocList->remove((*i));
		      break;
	      }
    }
};

Перед самым завершением программы наш список allocList содержит ссылки на блоки памяти, котороые не были освобождены. Все, что нужно сделать - вывести эту информацию куда-нибудь. В нашем проекте мы выведем список неосвобожденных участков памяти в окно вывода отладочных сообщений Visual C++:

void DumpUnfreed() {
	AllocList::iterator i;
	DWORD totalSize = 0;
	char buf[1024];

	if(!allocList) return;

	for(i = allocList->begin(); i != allocList->end(); i++) {
		sprintf(buf, "%-50s:\tLINE %d,\tADDRESS %d %d unfreed",
		      (*i)->file, (*i)->line, (*i)->address, (*i)->size);
		OutputDebugString(buf);
		totalSize += (*i)->size;
		}
	sprintf(buf, "--------------------------------------------------");
	OutputDebugString(buf);
	sprintf(buf, "Total Unfreed: %d bytes", totalSize);
	OutputDebugString(buf);
	};

Надеюсь, этот проект сделает ваши баг-листы короче, а программы устойчивее. Удачи!

Источник: www.infocity.kiev.ua

Оставить комментарий

Оставлять комментарии могут только зарегистрированные пользователи.

Если вы не являетесь зарегистрированным пользователем, то вам необходимо зарегистрироваться. Регистрация бесплатна. Если вы уже зарегистрированы на CodeNet, то вам необходимо ввести логин и пароль в верхней (Alt-U) части страницы.

Комментарии

1. Мирк / 12 августа 2010, 01:18:42
Мне нравитсяМне не нравится

Легче юзать готовые утилиты. Например Deleaker

2. Zavulon85 / 16 ноября 2009, 18:37:42
Мне нравитсяМне не нравится

Да проще пользоваться готовыми утилитами для поиска и локализации утечек памяти. Например - Deleaker ( http://deleaker.ru/ ) Уже второй месяц юзаю. Мне очень нравится.

3. Zavulon85 / 21 сентября 2009, 12:31:54
Мне нравитсяМне не нравится

Да легче тулзами готовыми пользоваться, Деликером, например: http://deleaker.ru/

4. id_Zebrikoff / 08 ноября 2006, 22:24:34
Мне нравитсяМне не нравится

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

5. Tubrik / 21 июля 2006, 15:00:04
Мне нравитсяМне не нравится

#include <list>
#include <algorithm>

using namespace std;

#ifndef _ALLOC_
#define _ALLOC_
typedef struct {
DWORD address;
DWORD size;
char file[64];
DWORD line;
} ALLOC_INFO;

typedef list<ALLOC_INFO> AllocList;

AllocList *allocList;
#endif

void AddTrack(DWORD addr, DWORD asize, const char *fname, DWORD lnum)
{
ALLOC_INFO *info;

if(!allocList) {
allocList = new(AllocList);
}

info = new(ALLOC_INFO);
info->address = addr;
strncpy(info->file, fname, 63);
info->line = lnum;
info->size = asize;
allocList->insert(allocList->begin(), *info);
};

void RemoveTrack(DWORD addr)
{
AllocList::iterator i;

if(!allocList)
return;
for(i = allocList->begin(); i != allocList->end(); i++)
{
if((i)->address == addr)
{
allocList->erase(i);
break;
}
}
};
void DumpUnfreed()
{
AllocList::iterator i;
DWORD totalSize = 0;
char buf[1024];

if(!allocList) return;

for(i = allocList->begin(); i != allocList->end(); i++)
{
sprintf(buf, "%-50s:\tLINE %d,\tADDRESS %d %d unfreed",
(i)->file, (i)->line, (i)->address, (i)->size);
OutputDebugString(buf);
totalSize += (i)->size;
}
sprintf(buf, "--------------------------------------------------");
OutputDebugString(buf);
sprintf(buf, "Total Unfreed: %d bytes", totalSize);
OutputDebugString(buf);
};
//.....................................................................................................



#ifdef _DEBUG

inline void * __cdecl operator new(unsigned int size, const char *file, int line)
{
void *ptr = (void *)malloc(size);
AddTrack((DWORD)ptr, size, file, line);
return(ptr);
};

inline void __cdecl operator delete(void *p)
{
RemoveTrack((DWORD)p);
free(p);
};

#endif

#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW


изменено:
обращения типа (*i)->address на i->address;
allocList->remove((*i)); на allocList->erase(i); т.к. в list при обращении к remove() к строке if (*_First == _Val) не получается сравнивать структуры и компилятор выругивается

в Си я новичок поэтому пришлось потратить достаточно времени для устранения ошибок, но одну пока не смог разрешить: автор рекомендует воткнуть все это дело в stdafx.h, но тогда возникают проблемы, т.к. #include "stdafx.h" имеется в нескольких модулях и возникают сообщения компилятора типа:
NewSocks error LNK2005: "void __cdecl RemoveTrack(unsigned long)" (?RemoveTrack@@YAXK@Z) already defined in NewSocks.obj

#ifndef
#define
#endif не помогает, как это разрешить?

6. Tubrik / 20 июля 2006, 18:54:33
Мне нравитсяМне не нравится

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

7. Tubrik / 20 июля 2006, 16:37:27
Мне нравитсяМне не нравится

А как же delete в AddTrack

8. kr4v4 / 14 июля 2005, 17:49:38
Мне нравитсяМне не нравится

А как же конструкторы, деструкторы как их вызывать при новых new delete
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог