Як виявити витік пам'яті

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

Як виявити витік пам'яті

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

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

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

Реалізація

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

Як бачите, перевизначення операторів відбувається в блоці # ifdef / # endif. Це убезпечує наш код від впливу на реліз модульна програми. Ви, напевно, помітили, що тепер оператор new має три параметри замість одного. Два додаткових параметра містять ім'я файлу і номер рядка, в якій виділяється пам'ять. Це зручно для виявлення конкретного місця, де відбувається помилка. Однак код наших проектів як і раніше посилається на оператор new, який приймає один параметр. Для виправлення цієї невідповідності потрібно додати наступний фрагмент

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

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

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

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

Сподіваюся, цей проект зробить ваші баг-листи коротше, а програми стійкіше. Успіхів!

#include
#include

using namespace std;

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

typedef list 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 [тисячі двадцять чотири];

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

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);
>;

#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) не виходить порівнювати структури і компілятор виругівается

#ifndef
#define
#endif не допомагає, як це вирішити?

Схожі статті