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

Ваш аккаунт

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

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

Подписчиков: -1
Последний выпуск: 19.06.2015

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

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

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

Введение

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

Для обнаружения подобных ошибок создано специализированное программное обеспечение (типа 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

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

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
14K
30 ноября 2005 года
John1980
6 / / 30.11.2005
Мне нравитсяМне не нравится
26 октября 2012, 00:25:38
У меня есть вопрос: если у меня есть иерархия классов и в базовом классе и я не объявлю виртуальным деструктор, отработает ли данный вариант?
2.
59K
13 мая 2010 года
Мирк
3 / / 13.05.2010
+0 / -1
Мне нравитсяМне не нравится
12 августа 2010, 01:18:42
Легче юзать готовые утилиты. Например Deleaker
3.
51K
18 июля 2009 года
Zavulon85
1 / / 18.07.2009
+0 / -1
Мне нравитсяМне не нравится
16 ноября 2009, 18:37:42
Да проще пользоваться готовыми утилитами для поиска и локализации утечек памяти. Например - Deleaker ( http://deleaker.ru/ ) Уже второй месяц юзаю. Мне очень нравится.
4.
51K
18 июля 2009 года
Zavulon85
1 / / 18.07.2009
+1 / -1
Мне нравитсяМне не нравится
21 сентября 2009, 12:31:54
Да легче тулзами готовыми пользоваться, Деликером, например: http://deleaker.ru/
5.
20K
08 ноября 2006 года
id_Zebrikoff
8 / / 08.11.2006
+1 / -0
Мне нравитсяМне не нравится
8 ноября 2006, 22:24:34
прога эта не пашет:) но вот поковырявшись я ее неплохо доработал.
кому надо, давайте создадим новую тему и обсудим внесенные доработки
6.
20K
20 июля 2006 года
Tubrik
0 / / 20.07.2006
+2 / -0
Мне нравитсяМне не нравится
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 не помогает, как это разрешить?
7.
20K
20 июля 2006 года
Tubrik
0 / / 20.07.2006
Мне нравитсяМне не нравится
20 июля 2006, 18:54:33
вообще было бы неплохо проверить работостпособность кода, который используется как пример
8.
20K
20 июля 2006 года
Tubrik
0 / / 20.07.2006
+1 / -0
Мне нравитсяМне не нравится
20 июля 2006, 16:37:27
А как же delete в AddTrack
9.
Аноним
+1 / -0
Мне нравитсяМне не нравится
14 июля 2005, 17:49:38
А как же конструкторы, деструкторы как их вызывать при новых new delete
9.1.
84K
10 июля 2012 года
eldarbal
0 / / 10.07.2012
+0 / -3
Мне нравитсяМне не нравится
10 июля 2012, 20:23:22
разве c++ не работает с памятью?
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог