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

При розробці великих програм, що оперують великими обсягами інформації на перше місце при налагодженні постає проблема виявлення неправильного розподілу пам'яті. Суть проблеми полягає в тому, що якщо ми виділили ділянку пам'яті, а потім звільнили не весь виділений обсяг, то утворюються блоки пам'яті, які позначені як зайняті, але насправді вони не використовуються. При тривалій роботі програми такі блоки можуть накопичуватися, приводячи до значної витрати пам'яті. Для виявлення подібних помилок створено спеціалізоване програмне забезпечення (типу 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: LINE %d, ADDRESS %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);
};


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

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*