Використання альтернативного аллокатора пам'яті в проекті на C/C++

Ця стаття написана передусім для програмістів C/C++, що використовують у своїй роботі Visual Studio 2013. Оскільки я, як кажуть, totally windows guy, то я не можу оцінити корисність цієї статті для програмістів, які не використовують цю середу в своїй роботі. Отже.

Не секрет, що стандартний аллокатор new/delete/malloc/free в мові C/C++ не блищить швидкодією. Звичайно, все залежить від реалізації, але, якщо говорити про неї від компанії Microsoft, то це факт. Крім того, стандартна реалізація аллокатора володіє ще одним фатальним недоліком — фрагментацією пам'яті. Якщо у вашій програмі відбуваються часті виділення/звільнення пам'яті, ви можете виявити, що через кілька годин роботи ваша програма впала через брак пам'яті, хоча вільної пам'яті ще досить — просто в результаті фрагментації в пулі аллокатора не залишилося вільного ділянки достатнього розміру. (Це, до речі, абсолютно реальний випадок, який стався на одному з проектів, в якому я брав безпосередню участь.)

На щастя, існують аллокаторы, які позбавлені обох цих недоліків. У свій час я спробував dlmalloc з тих пір завжди використовую його в своїх проектах.

Хочу поділитися з вами способом підключення dlmalloc до проекту Visual Studio C/C++.
Спосіб, який я використовую, примітний тим, що дозволяє використовувати альтернативний аллокатор абсолютно для всіх аллокаций, які тільки можуть трапитися у вашій програмі. Так, простий спосіб (тобто заміна викликів malloc на dlmalloc) не досягає цього ефекту. Наприклад, ви підключили сторонню бібліотеку, яка виділять пам'ять з допомогою malloc. Більш того, деякі виклики стандартних функцій з stdlib також виділяють пам'ять функцією malloc і у вас немає можливості цьому перешкодити… Чи є? Є.

Суть способу

Суть способу в тому, щоб змусити ликовщик використовувати вашу реалізацію malloc/free/new/delete замість стандартної. Але як це зробити? Коли я тільки почав досліджувати це питання, моєю першою спробою була досить дурна ідея: пропатчити в runtime тіло malloc/free в пам'яті, помістивши туди безумовний jmp на мій код. Чи треба пояснювати, чому ця ідея дурна? Хоча все працювало, але радості цей спосіб не приносив. В підсумку я прийшов до іншого вирішення, а саме — заборонити линковщику взагалі використовувати стандартну бібліотеку libcmt, в якій і знаходиться стандартний аллокатор. Але і цей спосіб мав істотним недоліком, а саме, в цій бібліотеці досить багато інших корисних і не дуже функцій, написати до яких заглушки було категорично неможливо.
Тоді я став досліджувати можливість взяти стандартну бібліотеку (буквально файл libcmt.lib) і викинути з нього все зайві. Виявилося, що це можливо і в результаті цей спосіб я і використовую.

Невеликий відступЯ кажу про файл libcmt.lib, однак ви повинні розуміти, що все теж саме справедливо і для libc.lib. Пояснення різниці між цими бібліотеками виходить за рамки цієї статті.

Технічні подробиці

Спочатку виконаємо команду:

lib.exe /LIST libcmt.lib

На виході отримаємо список файлів obj, які ця бібліотека містить. Для libcmt.lib з Visual Studio 2013 цей список виглядає приблизно так:

f:\binaries\Intermediate\vctools\crt_bld\SELF_X86\crt\prebuild\INTEL\mt_lib\chandler4.obj
f:\binaries\Intermediate\vctools\crt_bld\SELF_X86\crt\prebuild\INTEL\mt_lib\chandler4gs.obj
f:\binaries\Intermediate\vctools\crt_bld\SELF_X86\crt\prebuild\INTEL\mt_lib\chkesp.obj
f:\binaries\Intermediate\vctools\crt_bld\SELF_X86\crt\prebuild\INTEL\mt_lib\eh3valid.obj
f:\binaries\Intermediate\vctools\crt_bld\SELF_X86\crt\prebuild\INTEL\mt_lib\exsup.obj
f:\binaries\Intermediate\vctools\crt_bld\SELF_X86\crt\prebuild\INTEL\mt_lib\exsup2.obj
... (і так далі)

На щастя, практично всі функції по роботі з пам'яттю знаходяться в окремих файлах obj, що, власне, і робить цей спосіб можливим.
Тобто нам залишається вирізати з тіла бібліотеки всі непотрібні obj-файли.
Утиліта lib.exe з ключем /remove як раз робить те, що нам потрібно.

Реалізація

Власне, вихідні коди я виклав на гітхабі.

Якщо у вас вже встановлена Visual Studio 2013, досить запустити make_libcmt_nomem.cmd, який виконає всю роботу і створить обрізаний файл libcmt_nomem.lib, який можна підключати замість повного libcmt.

У своїй роботі скрипт використовує unix утиліту grep. Якщо у вас не встановлені UnixUtils, настійно рекомендую це зробити (наприклад, звідси).

Але це ще не все. Від стандартного аллокатора ми позбулися. Але біда в тому, що заодно ми позбулися й деякої стандартної функціональності, яка, на жаль, невіддільна від аллокатора. Тому мною були написані потрібні заглушки, які ви можете знайти у файлі include/crtfunc.h (там же, на гітхабі).

Спосіб застосування

  1. Отримуємо обрізану версію стандартної бібліотеки з допомогою скрипта make_libcmt_nomem.cmd і кладемо її в доступне для зв'язування місце;
  2. Відключаємо у проекті використання стандартної бібліотеки libcmt (Ignore Specific Default Libraries" «libcmt» в опціях лінкера Configuration Properties->Linker->Input);
  3. У кожному c++ файл в проекті робимо "#include crtfunc.h" з исходников;
  4. Підключаємо dlmalloc до проекту.
Я не розписую докладно кожен пункт, оскільки, якщо ви прочитали цю статтю і зрозуміли її, подробиці вам і не потрібні. Єдиний момент: підключати crtfunc.h слід саме в C++ (не C) файл. Якщо ваш проект написаний на C, вам слід додати до проекту порожній .cpp файл і включити в нього crtfunc.h. Втім, ніхто не забороняє вам взяти в руки напилок.

PS. Насправді, не dlmalloc'ом єдиним. Існують і інші, вельми гідні аллокаторы. Вихідні файли розраховані на dlmalloc, але це не принципово. Мінімальним втручанням у crtfunc.h можна домогтися використання будь-якого іншого аллокатора.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.