Аллокации, теги и хуки. Часть 1

Раз уж начал говорить про память, покажу простой способ навешать теги на аллокации с возможность хука на них, с помощью которого можно сделать чудо вещи, например, трекинг памяти.

Для начала нам нужны простые функции выделения памяти, которые мы будем использовать вместо системных. Так же понадобятся енумы для тегов аллокаций и действий хипа.

Где-то в хидере в самом низу вашего проекта:

namespace bm {  
    // теги для аллокаций
    enum E_HEAP_TAGS {
        HEAP_BM_SYSTEM_TAG,
        HEAP_STL_STRINGS_TAG,
        HEAP_STL_CONTAINERS_TAG,
        HEAP_UNKNOWN_TAG
    };

    // енум для действий хипа
    enum E_HEAP_ACTS {
        HEAP_ACT_ALLOC,
        HEAP_ACT_FREE
    };

    // тип функции для хука
    typedef void (*THeapHook)(
        E_HEAP_ACTS act, E_HEAP_TAGS tag,
        void* ptr, size_t size);

    // функции выделения и очищения памяти
    void* Heap_Alloc ( size_t size, E_HEAP_TAGS tag );
    void  Heap_Free  ( void*   ptr, E_HEAP_TAGS tag );

    // функция установки пользовательского хука
    THeapHook Heap_SetHook ( THeapHook hook );
}

Реализуем всё то, что объявили выше. Где-то в исходниках:

// макросы для системных функций выделения и очистки памяти
#define BM_Sys_Alloc ::malloc
#define BM_Sys_Free  ::free

namespace bm {  
    namespace allocs_detail {
        // глобальный текущий хук, по-умолчанию пустой
        static THeapHook heap_hook = NULL;

        // функция вызова текущего хука, будет зваться из наших Heap_Alloc и Heap_Free
        static void CallHeapHook(
            E_HEAP_ACTS act, // действие
            E_HEAP_TAGS tag, // тег
            void* ptr,       // память которую выделяем\удаляем
            size_t size)     // размер куска памяти
        {
            if ( heap_hook )
                heap_hook(act, tag, ptr, size);
        }
    }

    // реализация нашего выделения памяти
    void* Heap_Alloc(size_t size, E_HEAP_TAGS tag) {
        // выделяем память с помощью системной функции и зовём хук
        void* ptr = BM_Sys_Alloc(size);
        allocs_detail::CallHeapHook(HEAP_ACT_ALLOC, tag, ptr, size);
        return ptr;
    }

    // реализация нашего освобождения памяти
    void Heap_Free(void* ptr, E_HEAP_TAGS tag) {
        // зовем сначало хук, пока память жива, потом удаляем системной функцией
        allocs_detail::CallHeapHook(HEAP_ACT_FREE, tag, ptr, 0);
        BM_Sys_Free(ptr);
    }

    // реализация установки пользовательского хука
    THeapHook Heap_SetHook( THeapHook hook ) {
        // устанавливаем новый и возвращаем старый (вдруг кому понадобится)
        using allocs_detail::heap_hook;
        THeapHook last = heap_hook;
        heap_hook = hook;
        return last;
    }
}

С функциями закончили. Теперь вместо обычных malloc и free нужно звать Heap_Alloc и Heap_Free с нужными тегами, не забывая про "3rd party"-библиотеки, которые в большинстве своем предлагают определять для них функции выделения памяти, если же нет, то придется ручками менять, а лучше просто гнать такие библиотеки в шею, ибо не дело это :)

Ок, с malloc и free всё понятно, что же делать с new и delete? Всё просто - переопределяем их на свои:

inline void* operator new(size_t size, bm::E_HEAP_TAGS tag)  
{ return bm::Heap_Alloc(size, tag); }

inline void* operator new[](size_t size, bm::E_HEAP_TAGS tag)  
{ return bm::Heap_Alloc(size, tag); }

inline void* operator new(size_t size)  
{ return bm::Heap_Alloc(size, bm::HEAP_UNKNOWN_TAG); }

inline void* operator new[](size_t size)  
{ return bm::Heap_Alloc(size, bm::HEAP_UNKNOWN_TAG); }

inline void operator delete(void* ptr, bm::E_HEAP_TAGS tag)  
{ bm::Heap_Free(ptr, tag); }

inline void operator delete[](void* ptr, bm::E_HEAP_TAGS tag)  
{ bm::Heap_Free(ptr, tag); }

inline void operator delete(void* ptr)  
{ bm::Heap_Free(ptr, bm::HEAP_UNKNOWN_TAG); }

inline void operator delete[](void* ptr)  
{ bm::Heap_Free(ptr, bm::HEAP_UNKNOWN_TAG); }

Теперь:
new TestClass() - будет выделять память через наши функции с HEAP_UNKNOWN_TAG тегом.
new (HEAP_BM_SYSTEM_TAG) TestClass() - с тегом заданным в параметре new.
Так же, по вкусу, можно переопределить nothrow new, если сие нужно для вашего проекта.

И с этим разобрались. Понятное дело, что вызывать new таким образом неудобно, по-этому можно написать немного помогающего кода:

namespace bm {  
    template < E_HEAP_TAGS TAG >
    class Allocable {
        public:
            void* operator new     (size_t size) { return ::operator new     (size, TAG); }
            void* operator new[]   (size_t size) { return ::operator new[]   (size, TAG); }
            void  operator delete  (void*   ptr) {        ::operator delete  ( ptr, TAG); }
            void  operator delete[](void*   ptr) {        ::operator delete[]( ptr, TAG); }
    };
}

Простой классик, наследуясь от которого можно получить нужный тег по-умолчанию, например:

class Texture : public Allocable<E_HEAP_TEXTURES_TAG>  
{...};

// память будет выделена с тегом E_HEAP_TEXTURES_TAG
Texture* texture = new Texture();  

В следующей части расскажу как это всё прикрутить к STL контейнерам, ибо их-то аллокации тоже чекать надо.