Пошук помилок під час роботи з пам’яттю

Автор: Олександр Шаргін Джерело: RSDN Magazine #0

пошук

Помилки під час роботи з пам'яттю належать до найпоширеніших помилок, із якими доводиться стикатися програмісту мовою C++. Тому в Visual C++ включено спеціальний засіб для пошуку подібних помилок –налагоджувальна бібліотека часу виконання(Debug CRT, DCRT). Вона включає функції для роботи зналагоджувальною купою(debug heap), яка допоможе вам виявити:

  • запису за кордон виділеного блоку пам'яті
  • записи у вже звільнений блок пам'яті
  • витоку пам'яті

Перш ніж вивчати функції, призначені для роботи з купою налагодження, коротко розглянемо принципи її роботи.

Влаштування налагоджувальної купи

У DCRT існують спеціальні функції для розподілу пам'яті з купи налагодження -_malloc_dbg,_free_dbgі деякі інші. У налагоджувальній версії програми весь розподіл пам'яті йде через них, тому що через них реалізуються стандартні функції malloc /freeі операториnew/delete. Функція_malloc_dbgвиділяє блоки пам'яті наступного виду (рис. 15).

помилок
Малюнок 15. Блок у налагоджувальній купі

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

  • Попередній блок/наступний блок. Ці два поля служать для організації всіх блоків одним великим двозв'язним списком.
  • Ім'я файлу/номер рядка. Ці поля однозначно задають рядок у програмі, у якій було виконано розподіл цього блоку.
  • Довжина блоку. Розміробласті даних у байтах.
  • Тип блоку. Про типи ми поговоримо трохи згодом.
  • Серійний номер. Число, що унікально ідентифікує блок. Оскільки одна і та ж інструкція у програмі може розподіляти безліч блоків, імені файлу та номера рядка недостатньо, щоб однозначно ідентифікувати блок. Серійний номер дозволяє це зробити. Нижче ми побачимо, як це можна скористатися.

Заборонені області розташовуються по обидва боки області даних. У Visual C++ 6.0 кожна з них має довжину 4 байти. При розподілі блоку ці області заповнюються певним значенням_bNoMansLandFill(на даний момент воно дорівнює 0xFD). Якщо програма перезапише ці значення, буде зафіксовано помилку запису за межі виділеного блоку. Сама область даних спочатку заповнюється значенням_bCleanLandFill(зараз одно 0xCD). Завдяки цьому можна легко відрізнити неініціалізовані області блоку від ініціалізованих.

Звільненням блоків пам'яті займається функція_free_dbg. За умовчанням вона просто повертає блок у купу. Але можна увімкнути такий режим, в якому вона залишатиме всі блоки в пам'яті. Такі блоки позначатимуться як звільнені (free) і заповнюватимуться значенням_bDeadLandFill(зараз це 0xDD). Якщо виявиться, що програма перезаписала це значення, фіксується помилка запису вже звільнений блок. Такий режим роботи_free_dbgкорисний, але призводить до підвищеної витрати пам'яті.

Тепер розглянемо різноманітні типи блоків.

  • _NORMAL_BLOCK. Нормальний блок, що виділяється функцієюmalloc(або операторомnew).
  • _CRT_BLOCK. Блок, що виділяється усередині CRT. За замовчуванням такі блоки ігноруються під час аналізу - мається на увазі,що CRT працює правильно. Якщо у вас виникнуть сумніви щодо цього, облік блоків даного типу можна включити.
  • _CLIENT_BLOCK. Клієнтський блок. Цей блок схожий на_NORMAL_BLOCK, але використовується для зберігання інформації певного виду (наприклад, об'єкта деякого класу). Для такого блоку можна написати спеціальну функцію дампа, яка займатиметься виведенням діагностичної інформації у зручній для сприйняття формі. Наприклад, у бібліотеці MFC всі об'єкти, похідні відCObject, розміщуються у блоках клієнтського типу. Для виведення діагностичної інформації використовується функціяDumpвідповідного об'єкта. Нижче побачимо, як писати власні функції дампа. Клієнтські блоки можна розбити на підтипи. У цьому випадку тип блоку задається як(_CLIENT_BLOCK(SUBTYPE)