Программирование arrow Visual C++ arrow Как обнаружить утечку памяти

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

При разработке больших приложений, оперирующих большими объемами информации на первое место при отладке встает проблема обнаружения неправильного распределения памяти. Суть проблемы состоит в том, что если мы выделили участок памяти, а затем освободили не весь выделенный объем, то образуются блоки памяти, которые помечены как занятые, но на самом деле они не используются. При длительной работе программы такие блоки могут накапливаться, приводя к значительному расходу памяти. Для обнаружения подобных ошибок создано специализированное программное обеспечение (типа 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<ALLOC_INFO*> 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:\t\tLINE %d,\t\tADDRESS %d\t%d unfreed\n",
(*i)->file, (*i)->line, (*i)->address, (*i)->size);
OutputDebugString(buf);
totalSize += (*i)->size;
}
sprintf(buf, "--------------------------------------------------\n");
OutputDebugString(buf);
sprintf(buf, "Total Unfreed: %d bytes\n", totalSize);
OutputDebugString(buf);
};

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

 
« Предыдущая статья   Следующая статья »


  • Visual C++, Работа с СУБД Oracle через интерфейс OCCI
    OCCI - расшифровывается как Oracle C++ Call Interface и представляет собой специализированное апи для работы с СУБД Oracle используя C++ что в общем то явствует из названия. Для использования необходимо подключить заголовочный файл "occi.h"....
  • Visual C++, Задача Майхилла для Microsoft Visual C++
    О синхронизации процессов в среде Windows. Задача Майхилла - еще один (наряду с задачей RS-триггера) пример решения нетривиальных проблем создания сложных систем. Справившись с ней, мы научимся организовывать взаимодействие параллельно работающих компонентов сложных программных комплексов в жестких условиях. ...
  • Visual C++, Использование ODBC в Visual C++
    Класс CDatabase представляет собой класс, который обеспечивает связь с источником данных. Под источником данных может пониматься как непосредсвенно файл, в котором находится таблица, например dBase, так и файл с многими таблицами, например Microsoft Access или сервер баз данных Oracle, MS SQL Server и т.д. Для связи с источником данных используется интерфейс ODBC. У данного класса есть папа в виде класса
  • Visual C++, Создание простого приложения с плагинами
    В этой статье описываются принципы и решения, применяемые при проектировании приложений, которые будут использовать внешние, динамически подключаемые, модули. Эта статья более ориентирована на тех, кто хочет использовать механизмы подключения/отключения функциональности приложения, наподобии механизма Aobe Photoshop или Far, а не просто многократного использования кода в разных приложениях....
  • Visual C++, Работа с 1C Предприятие из Visual C++
    В данной статье показано, как можно работать с 1С Предприятием из С++ с помощью OLE DB. Так же она будет интересна тем, кто не пользуется C++, но хочет узнать подробности "а как оно устроено внутри 1С". В данной статье речь пойдет об 1С Предприятии версии 7.7. Полагаю, что в версии 8 мало что изменилось. Предполагается, что читатель хотя бы чуть-чуть знаком с 1С Предприятием. Так же предполагается, что вы изучали официальное руководство 1С по вопросам OLE DB (часть вторая описани...
  • Visual C++, Как самому сделать plug-in к FAR на Visual C++
    Трудно найти человека, которые не знает или не использует Far - IMHO лучший клон NC для Windows. Кроме того, что это просто очень хороший файл менеджер, к нему есть огромное количество plug-in модулей. Plug-in модуль это DLL-файл, который вместо стандартных Windows функций по работе с монитором, клавиатурой и т.д. обращается к функциям Far-а. Far поддерживает весь набор функций для работы в текстовом режиме. Установка plug-in модуля происходит предельно просто - DLL файл и файлы данных коп...
  • Visual C++, Использование директивы #import в Visual C++
    В данной статье я попытаюсь объяснить то, как работает эта директива и привести несколько примеров её использования. Надеюсь, после этого вы тоже найдёте её полезной.  Директива #import введена в Visual C++, начиная с версии 5.0. Её основное назначение облегчить подключение и использование интерфейсов COM, описание которых реализовано в библиотеках типов....
  • Visual C++, Создание VxD на Visual C++ без ассемблерных модулей
    Виртуальные драйверы устройств (VxD) в Windows во многих случаях являются единственным «честным» способом обхода ограничений, установленных системой для приложений Win32: невозможности прямого доступа к портам ввода-вывода и служебной памяти, эффективной обработки аппаратных прерываний, использования сервисных функций существующих VxD и т.п. Кроме того, без VxD не обходится практически ни один полноценный драйвер физического или виртуального устройства....