Для програмерів тіпси і Трікс ловимо memory leaks

У «Диспетчері завдань» дуже часто можна побачити, що деякі програми займають абсолютно непристойне кількість пам'яті. Особливо це властиво інтернет-браузерів. Рано чи пізно перед кожним розробником постає завдання вилову витоків пам'яті. Сьогодні ми дізнаємося, як це зробити в мові C ++ на MSVC.

Шукати витоку пам'яті ми будемо в ОС Windows, а для складання коду використовувати компілятор від Microsoft. Існує безліч способів уникнути мемори ликів, але основне правило цієї боротьби можна сформулювати як «класти на місце все, що взяли». На жаль, в «бойовому КОДІНГ» таке не завжди можливо. Банальний людський фактор або спрацював exception запросто може скасувати виконання оператора delete. У цій статті ми не будемо розглядати, що потрібно робити, а що не варто, щоб пам'ять не витікала. Ми будемо діяти в контексті вже наявної проблеми: витік є і нам треба її перекрити.
Для вирішення цього завдання багато програмістів використовують сторонні бібліотеки (а найкрутіші пишуть власні менеджери пам'яті), але ми почнемо з чогось простіше - наприклад, скористаємося засобами Debug CRT.

Для використання Debug CRT треба підключити відповідний Хідер і включити використання Debug Heap Alloc Map. Робиться це лише кількома рядками коду:

Підключення Debug CRT

#ifdef _DEBUG
#include
#define _CRTDBG_MAP_ALLOC
#endif

Після цих дій при виділенні пам'яті через new і malloc () дані будуть обертатися в спеціальну структуру _CrtMemBlockHeader. За допомогою цієї обгортки ми зможемо дізнатися ім'я файлу і рядок, в якій резервувалася лікнутая пам'ять, її об'єм і самі дані. Всі записи об'єднані в двусвязний список, тому по ньому можна легко пробігтися і знайти проблемні ділянки.

структура _CrtMemBlockHeader

typedef struct _CrtMemBlockHeader
struct _CrtMemBlockHeader * pBlockHeaderNext;
struct _CrtMemBlockHeader * pBlockHeaderPrev;
char * szFileName;
int nLine;
size_t nDataSize;
int nBlackUse;
long lRequest;
unsigned char gap [nNoMansLandSize];
unsigned char data [nDataSize];
unsigned char anotherGap [nNoMansLandSize];

Щоб пройтися за цим списком, потрібно скористатися функцією _CrtDumpMemoryLeaks (). Вона не приймає ніяких параметрів, а просто виводить список утекших блоків пам'яті. Але, на жаль, вона нічого не говорить нам про фото і рядку, в яких виділялася пам'ять. Результат роботи цієї функції виглядає приблизно так:

Висновок _CrtDumpMemoryLeaks ()

Detected memory leaks!
Dumping objects ->
normal block at 0x00128788, 4 bytes long.
Data: <> 00 00 00 00
normal block at 0x00128748, 4 bytes long.
Data: <> 00 00 00 00
Object dump complete.

Ось воно як: в Microsoft Visual C ++ 6.0 в файлі crtdbg.h мало місце перевизначення функції new, яке повинно було точно показати файл і рядок, в якому відбувалося виділення пам'яті. Але воно не давало бажаного результату - FILE: LINE завжди розгорталися в crtdbg.h file line 512. У наступних версіях Microsoft взагалі прибрали цю фічу, і весь тягар відповідальності ліг на програмістів. Зробити нормальний висновок можна за допомогою наступного перевизначення:

перевизначення new

#define new new (_NORMAL_BLOCK, __FILE__, __LINE__)

Цю рядок бажано винести в який-небудь загальний заголовки і підключати його після crtdbg.h. Тепер перед нами стоїть завдання записати все це в якійсь лог або хоча б виводити в консоль. Для перенаправлення виведення нам будуть потрібні дві функції: _CrtSetReportMode і _CrtSetReportFile. Другим параметром _CrtSetReportFile може бути хендл нашого лог-файлу або прапор виведення в stdout.

перенаправлення виводу

_CrtSetReportMode (_CRT_WARN, _CRTDBG_MODE_FILE);
// виводимо всі в stdout
_CrtSetReportFile (_CRT_WARN, _CRTDBG_FILE_STDOUT);

У цього методу є ще одна проблемка - він виводить інформацію про пам'ять, яка витекла, а просто не встигла повернутися. Це, наприклад, може бути якась глобальна змінна або об'єкт. Нам потрібно якось видалити ці шматки пам'яті з виведення _CrtDumpMemoryLeaks (). Робиться це в такий спосіб:

Обмеження зони дії _CrtDumpMemoryLeaks ()

int _tmain (int argc, _TCHAR * argv [])
_CrtMemState _ms;
_CrtMemCheckpoint (_ms);
// some logic goes here.
_CrtMemDumpAllObjectsSince (_ms);
return 0;
>

Ми записуємо початковий стан пам'яті в спеціальну структуру за допомогою функції _CrtMemCheckpoint (), а в кінці, використовуючи _CrtMemDumpAllObjectsSince (), виводимо все, що спливло після того, як ми зробили зліпок пам'яті.

Ось так ось, за допомогою нехитрих функцій Debug CRT, ми можемо досить ефективно боротися з меморі ликами в нашій програмі. Звичайно, це не замінить серйозних бібліотек по вилову витоків, але цілком підійде для невеликих проектів.

Visual Leak Detector

Visual Leak Detector - це вже стороння бібліотека, але по суті вона є надбудовою над Debug CRT, яку ми розглянули трохи раніше. Користуватися їй досить просто - треба всього лише включити заголовки vld.h в будь-який файл проекту. Але з двома застереженнями.

По-перше, якщо у нас в проекті є кілька бінарних модулів (DLL або EXE), то include для vld.h треба робити як мінімум в одному вихідному файлі для кожного модуля. Тобто, якщо у нас після компіляції на виході виходить module_1.dll і module_2.dll, то нам потрібно зробити #include як мінімум в module_1.h і в module_2.h.

По-друге, включення заголовки Visual Leak Detector має відбуватися після включення прекомпілед Хідер. Тобто, після stdafx.h і інших подібних файлів. Після виконання цих умов досить запустити програму в дебаг-збірці, і бібліотека відразу почне працювати.

Visual Leak Detector можна налаштувати під свої потреби. Конфиг зберігається в файлі vld.ini, який, в свою чергу, лежить в директорії з встановленим VLD. Файл з настройками можна скопіювати в папку з проектом, і тоді бібліотека буде використовувати цю копію з індивідуальними настройками для кожного проекту.
Параметрів для тюнінгу Visual Leak Detector предостатньо. Наприклад, можна налаштувати той же висновок в файл або в дебаг консоль студії. Робиться це ключем ReportTo. За замовчуванням там стоїть «debugger», але можна замінити це значення на «file» або «both». Сподіваюся, їх зміст пояснювати не потрібно.

Якщо ми включимо висновок в файл, то треба вказати шлях до цього файлу за допомогою параметра ReportFile. Також можна вибрати кодування файлу за допомогою ReportEncoding: unicode або ASCII.

Ще є цікава опція пошуку ликів бібліотеки в самій собі (SelfTest). Так, буває і таке. Якщо постаратися, то можна отримати в output щось на зразок цього:

ERROR: Visual Leak Detector: Detected a memory leak internal to Visual Leak Detector.

Ще VLD дозволяє обмежити розмір дампа пам'яті, що виводиться в лог, або взагалі придушити цей висновок, налаштувати глибину і метод проходки по стеку і так далі. Але це вже специфічні речі, які деяким можуть стати в нагоді, а деяким і немає. У загальному і цілому Visual Leak Detector простий, зручний і не вимагає багато коду для включення режиму пошуку витоків.

Все, що ми розглянули вище, було актуально для Windows і MS Visual Studio. Але є й інші ОС. Valgrind якраз для них. Він працює в Linux і Mac OS X і використовується не тільки для вилову мемори ликів, а й для налагодження пам'яті і профілювання (збір характеристик роботи програми). По суті, Valgrind є віртуальною машиною, що використовує методи JIT-компіляції. Її зусиллями програма не виконується безпосередньо на процесорі комп'ютера, а транслюється в так зване «проміжне представлення». З цією виставою і працює Valgrind. Точніше, працюють його інструменти, але про це трохи пізніше.

Після обробки проміжного представлення Valgrind переганяє все назад в машинний код. Такі дії значно (в 4-5 разів) уповільнюють виконання програми. Але це цілком обґрунтована плата за контроль витрати пам'яті. Як я говорив вище, Valgrind надає кілька інструментів. Найпопулярніший з них - Memcheck. Він замінює стандартне виділення пам'яті мови C власної реалізацією. Memcheck виявляє спроби використання неініціалізованої пам'яті, читання / запис після її звільнення і з кінця виділеного блоку, а також витоку пам'яті.

Є й інші інструменти, наприклад Addrcheck - легша, але менш функціональна версія Memcheck. Інструменти Helgrind і DRD використовуються для пошуку помилок в багатопотоковому коді. Інакше кажучи, Valgrind набагато більш потужна штука, ніж просто бібліотека з пошуку мемори ликів.

висновок

Витоку пам'яті - одна з найпоширеніших проблем в програмуванні. Навіть найдосвідченіші з нас можуть отримати парочку ликів. Інструментів для їх вилову безліч, і ці кілька сторінок повинні допомогти тобі почати боротьбу з memory leaks.

Покажи цю статтю друзям: