Newer
Older
libj / modules / j / base / src / memory.cpp
#include <iostream>

#include <memory>
#include <mutex>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <new>

// 本来の malloc, free を利用するため j/memory.hpp を読み込む前に
// J_MEMORY_RAW を定義することで、malloc, free のマクロを定義させない
#define J_MEMORY_RAW
#include <j/memory.hpp>

namespace j
{

    ////////////////////////////////////////////////////////////////////////
    //
    // メモリ管理 エントリ操作 (内部用)
    //
    namespace
    {
        // =====================================================================
        //  定数、変数宣言
        // =====================================================================
        constexpr int PADDING = sizeof(void *) * 2;
        bool isMemoryInitialized = false; //!< 初期化済みか否か
        MemoryEntry memHead;              //!< 管理メモリ先頭
        MemoryEntry memTail;              //!< 管理メモリ末尾
        MemoryEntry memError;             //!< エラー時の情報出力用
        std::recursive_mutex memMtx;      //!< 排他処理用

        // =====================================================================
        //  プロトタイプ宣言
        // =====================================================================
        std::new_handler getNewHandler();
        void init();
        void add(MemoryEntry *entry);
        void remove(MemoryEntry *entry);
        MemoryEntry *newEntry(
            MemoryEntry *entry, std::size_t alignment, std::size_t size,
            MemoryMark mark, const char *file, const char *func, int line);
        void deleteEntry(MemoryEntry *entry);
        void *allocate(
            size_t alignment, std::size_t size,
            MemoryMark mark, const char *file, const char *func, int line);
        void *reallocate(
            void *ptr, std::size_t size,
            MemoryMark mark, const char *file, const char *func, int line);
        void deallocate(
            void *ptr,
            MemoryMark mark, const char *file, const char *func, int line);
        const char *getMemoryMarkErrorMessage(MemoryMark mark);

        // =====================================================================
        //  内部関数
        // =====================================================================

        /**
         * new ハンドラを取得します。
         */
        std::new_handler getNewHandler()
        {
            std::new_handler p = std::set_new_handler(0);
            std::set_new_handler(p);
            return p;
        }

        /**
         * メモリ管理初期化
         */
        void init()
        {
            if (!isMemoryInitialized)
            {
                isMemoryInitialized = true;
                memHead.size = memTail.size = 0;
                memHead.mark = memTail.mark = MemoryMark::DELETED;
                memHead.file = memTail.file = "";
                memHead.func = memTail.func = "";
                memHead.line = memTail.line = 0;
                memHead._prev = memHead._next = &memTail;
                memTail._prev = memTail._next = &memHead;
                memHead._padding[0] = memTail._padding[0] = nullptr;
                memHead._padding[1] = memTail._padding[1] = nullptr;
                memHead._data = memTail._data = nullptr;

                // エラー処理用
                memError.size = 0;
                memError.mark = MemoryMark::DELETED;
                memError.file = "";
                memError.func = "";
                memError.line = 0;
                memError._prev = memError._next = nullptr;
            }
        }

        /**
         * 指定されたメモリエントリをメモリ管理に追加します。
         * リスナが登録されている場合、通知します。
         *
         * @param entry 追加するエントリ
         */
        void add(MemoryEntry *entry)
        {
            init();
            std::lock_guard<std::recursive_mutex> lock(memMtx);
            entry->_next = &memTail;
            entry->_prev = memTail._prev;
            memTail._prev->_next = entry;
            memTail._prev = entry;
            MemoryManager::listener.alloc(*entry);
        }

        /**
         * 指定されたメモリエントリをメモリ管理より削除します。
         * リスナが登録されている場合、通知します。
         *
         * @param entry 削除するエントリ
         */
        void remove(MemoryEntry *entry)
        {
            init();
            std::lock_guard<std::recursive_mutex> lock(memMtx);
            entry->_prev->_next = entry->_next;
            entry->_next->_prev = entry->_prev;
            MemoryManager::listener.free(*entry);
        }

        /**
         * エラー発生時の通知をします。
         * リスナが登録されていない場合、何もしません。
         *
         * @param msg エラーメッセージ
         * @param size メモリサイズ
         * @param mark メモリ状態
         * @param file ファイル
         * @param func 関数
         * @param line 行番号
         */
        void notifyError(const char *msg, std::size_t size,
                         MemoryMark mark, const char *file, const char *func, int line)
        {
            init();
            std::lock_guard<std::recursive_mutex> lock(memMtx);
            memError.size = size;
            memError.mark = mark;
            memError.file = file;
            memError.func = func;
            memError.line = line;
            MemoryManager::listener.error(memError, msg);
        }

        /**
         * MemoryEntry を構築します。
         *
         * @param entry エントリ
         * @param alignment アライメント
         * @param size 確保するメモリサイズ
         * @param mark メモリ状態
         * @param file ファイル名
         * @param func 関数名
         * @param line 行番号
         */
        MemoryEntry *newEntry(MemoryEntry *entry, std::size_t alignment, std::size_t size,
                              MemoryMark mark, const char *file, const char *func, int line)
        {
            MemoryEntry *newEntry = nullptr;
            // メモリ確保 [@see C++ Programming Language 3rd $14.4.5]
            for (;;)
            {
                // メモリ確保
                if ((entry == NULL) && (alignment > 0) && IS_MANAGED_ALIGNED(mark))
                {
                    newEntry = static_cast<MemoryEntry *>(MemoryManager::raw_aligned_alloc(
                        alignment, static_cast<size_t>(sizeof(MemoryEntry) + size + PADDING)));
                }
                else
                {
                    newEntry = static_cast<MemoryEntry *>(MemoryManager::raw_realloc(
                        entry, static_cast<size_t>(sizeof(MemoryEntry) + size + PADDING)));
                }

                if (newEntry != nullptr)
                { // 確保成功のため抜ける
                    break;
                }

                if (std::new_handler nhandler = getNewHandler())
                {
                    nhandler();
                }
                else
                {
                    return nullptr;
                }
            }

            newEntry->size = size;
            newEntry->mark = mark;
            newEntry->file = file;
            newEntry->func = func;
            newEntry->line = line;
            newEntry->_data = (newEntry + 1);
            return newEntry;
        }

        /**
         * MemoryEntry を破棄します。
         *
         * @param entry エントリ
         */
        void deleteEntry(MemoryEntry *entry)
        {
            entry->mark = MemoryMark::DELETED;
            entry->size = 0;
#if (IS_WINDOWS)
            if (IS_MANAGED_ALIGNED(entry->mark))
            {
                ::_aligned_free(entry);
                return;
            }
#endif
            MemoryManager::raw_free(entry);
        }

        /**
         * 指定されたサイズのメモリを確保します。
         * 確保に失敗した場合、nullptr を返します。
         * alignment > 0 の場合、アライメント指定でメモリを確保します。
         *
         * @param alignment アライメント
         * @param size 確保するメモリサイズ
         * @param mark メモリ状態
         * @param file ファイル名
         * @param func 関数名
         * @param line 行番号
         */
        void *allocate(size_t alignment, std::size_t size,
                       MemoryMark mark, const char *file, const char *func, int line)
        {
            void *dataPtr = nullptr;
            MemoryEntry *entry = newEntry(nullptr, alignment, size, mark, file, func, line);
            if (entry)
            {
                add(entry);
                dataPtr = entry->_data;
            }
            else
            { // メモリ確保に失敗したためエラー通知する。
                notifyError("can't allocate", size, mark, file, func, line);
            }
            return dataPtr;
        }

        /**
         * 指定された ptr のメモリサイズを変更します。
         * ptr = nullptr の場合は、allocate の alignment = 0 と同様の動作となります。
         * 確保に失敗した場合、nullptr を返します。
         *
         * @param ptr ポインタ
         * @param size 確保するメモリサイズ
         * @param mark メモリ状態
         * @param file ファイル名
         * @param func 関数名
         * @param line 行番号
         */
        void *reallocate(void *ptr, std::size_t size,
                         MemoryMark mark, const char *file, const char *func, int line)
        {
            if (size == 0)
            { // size が 0 の場合、free と等価
                deallocate(ptr, MemoryMark::ALLOCATED, file, func, line);
                return nullptr;
            }

            if (ptr == nullptr)
            { // ptr が nullptr の場合は、malloc と等価
                return allocate(0, size, mark, file, func, line);
            }

            void *dataPtr = nullptr;
            MemoryEntry *entry = static_cast<MemoryEntry *>(ptr);
            entry--;
            if (entry->mark == MemoryMark::ALLOCATED)
            {
                remove(entry);
                MemoryEntry *nEntry = newEntry(entry, 0, size, MemoryMark::ALLOCATED, file, func, line);
                if (nEntry)
                {
                    add(nEntry);
                    dataPtr = nEntry->_data;
                }
                else
                { // メモリ確保失敗 -> 元の領域は解放されていないためそのまま再管理する。
                    add(entry);
                    notifyError("can't realloc", size, mark, file, func, line);
                }
            }
            else
            { // ALLOCATED 以外はエラー
                const char *errMsg = getMemoryMarkErrorMessage(entry->mark);
                notifyError(errMsg, size, mark, file, func, line);
            }
            return dataPtr;
        }

        /**
         * 指定されたポインタのメモリを解放します。
         *
         * @param ptr 解放するメモリへのポインタ
         * @param mark 期待されるメモリ状態
         * @param file ファイル
         * @param func 関数名
         * @param line 行番号
         */
        void deallocate(void *ptr,
                        MemoryMark mark, const char *file, const char *func, int line)
        {
            if (ptr == nullptr)
            { // nullptr に対しては何もしない。
                return;
            }

            MemoryEntry *entry = static_cast<MemoryEntry *>(ptr);
            entry--;

            if (IS_MANAGED_MEMORY(entry->mark))
            { // 管理メモリ
                if ((entry->mark == mark) || (IS_MANAGED_ALIGNED(entry->mark) && (TO_NO_ALIGNED(entry->mark) == mark)))
                { // 期待するメモリ状態と合致、または、実際のメモリ状態が期待する状態のアライメント状態
                    remove(entry);
                    deleteEntry(entry);
                }
                else
                { // エラー処理
                    const char *errMsg = getMemoryMarkErrorMessage(entry->mark);
                    notifyError(errMsg, entry->size, mark, file, func, line);
                }
            }
            else
            { // 管理外メモリのため、通常の free で解放する。
                MemoryManager::raw_free(ptr);
            }
        }

        /**
         * reallocate におけるエラーメッセージを取得します。
         *
         * @param mark メモリ状態
         * @return エラーメッセージ
         */
        const char *getMemoryMarkErrorMessage(MemoryMark mark)
        {
            switch (mark)
            {
            case ALLOCATED:
                return "invalid pointer (memory allocated by allocated)";
            case ALLOCATED_ALIGNED:
                return "unspported pointer (memory allocated by 'aligned_alloc')";
            case ALLOCATED_NEW:
                return "invalid pointer (memory allocated by 'new')";
            case ALLOCATED_NEW_ARRAY:
                return "invalid pointer (memory allocated by 'new[]')";
            case DELETED:
                [[fallthrough]];
            default:
                return "invalid pointer";
            }
        }

    } // namespace

    ////////////////////////////////////////////////////////////////////////////
    //
    // メモリ管理
    //

    namespace MemoryManager
    {
        DefaultMemoryListener listener;      //!< 通知用メモリリスナ
        thread_local const char *gFile = ""; //!< 一時保存用 ファイル名
        thread_local const char *gFunc = ""; //!< 一時保存用 関数名
        thread_local int gLine = 0;          //!< 一時保存用 行番号

        /**
         * 管理している全メモリエントリを引数に指定されたハンドラを実行します。
         * ハンドラが false を返す場合、処理が中断されます。
         *
         * @param handler ハンドラ
         * @param arg ハンドラに渡す情報
         */
        void entries(bool (*handler)(const MemoryEntry &entry, void *arg), void *arg)
        {
            if (isMemoryInitialized)

            {
                std::lock_guard<std::recursive_mutex> lock(memMtx);
                bool isContinue = true;
                for (MemoryEntry *entry = memHead._next;
                     isContinue && (entry != &memTail);
                     entry = entry->_next)
                {
                    isContinue = handler(*entry, arg);
                }
            }
            // else 未初期化の場合は、エントリがないため何もしない。
        }

        /**
         * 指定されたハンドラが、true を返す管理メモリをクリア(削除)します。
         * 本メソッド実行時、管理しているメモリの情報を引数に指定された関数が実行されます。
         *
         * 使用例)
         * @code
         * bool canFree(const MemoryEntry&, void*)
         * {    // 管理しているメモリをすべてクリアする。
         *   return true;
         * }
         * MemoryManager::freeif(&canFree);
         * @endcode
         *
         * @param handler ハンドラ
         * @param arg ハンドラに渡す情報
         */
        void freeif(bool (*handler)(const MemoryEntry &entry, void *arg), void *arg)
        {
            if (isMemoryInitialized)
            {

                std::lock_guard<std::recursive_mutex> lock(memMtx);
                MemoryEntry *entry = memHead._next;
                while (entry != &memTail)
                {
                    MemoryEntry *nextEntry = entry->_next;
                    bool isFree = handler(*entry, arg);

                    if (isFree)
                    {
                        remove(entry);
                        deleteEntry(entry);
                    }
                    entry = nextEntry;
                }
            }
            // else 未初期化の場合は、エントリがないため何もしない。
        }

        /**
         * 指定されたサイズのメモリを確保します。
         *
         * @param size 確保するメモリサイズ
         * @param file メモリ確保ファイル名
         * @param func メモリ確保関数名
         * @param line メモリ確保行番号
         * @return 確保したメモリへのポインタ
         */
        void *malloc(std::size_t size, const char *file, const char *func, int line)
        {
            return allocate(0, size, MemoryMark::ALLOCATED, file, func, line);
        }

        /**
         * 指定されたサイズのメモリを確保します。
         *
         * @param alignement アライメント
         * @param size 確保するメモリサイズ
         * @param file メモリ確保ファイル名
         * @param func メモリ確保関数名
         * @param line メモリ確保行番号
         * @return 確保したメモリへのポインタ
         */
        void *aligned_alloc(std::size_t alignment, std::size_t size, const char *file, const char *func, int line)
        {
            return allocate(alignment, size, MemoryMark::ALLOCATED_ALIGNED, file, func, line);
        }

        /**
         * 指定されたサイズのメモリを確保します。
         *
         * @param nmemb 確保するメモリの要素数
         * @param size 要素のサイズ
         * @param file メモリ確保ファイル名
         * @param func メモリ確保関数名
         * @param line メモリ確保行番号
         * @return 確保したメモリへのポインタ
         */
        void *calloc(std::size_t nmemb, std::size_t size, const char *file, const char *func, int line)
        {
            if ((nmemb == 0) || (size == 0))
            {
                return nullptr;
            }

            if (nmemb > std::numeric_limits<std::size_t>::max() / size)
            { // オーバーフロー検知
                return nullptr;
            }

            std::size_t n = nmemb * size;
            void *ptr = allocate(0, n, MemoryMark::ALLOCATED, file, func, line);
            if (ptr)
            { // calloc のためゼロクリアする。
                std::memset(ptr, 0x00, n);
            }
            return ptr;
        }

        /**
         * ポインタが示すメモリブロックのサイズを変更します。
         *
         * @param ptr ポインタ
         * @param size 確保するメモリのサイズ
         * @param file メモリ確保ファイル名
         * @param func メモリ確保関数名
         * @param line メモリ確保行番号
         * @return 確保したメモリへのポインタ
         */
        void *realloc(void *ptr, std::size_t size, const char *file, const char *func, int line)
        {
            return reallocate(ptr, size, MemoryMark::ALLOCATED, file, func, line);
        }

        /**
         * 指定されたメモリを解放します。
         *
         * @param ptr 解放するメモリへのポインタ
         * @param file メモリ解放ファイル名
         * @param func メモリ解放関数名
         * @param line メモリ解放行番号
         */
        void free(void *ptr, const char *file, const char *func, int line)
        {
            deallocate(ptr, MemoryMark::ALLOCATED, file, func, line);
        }

        /**
         * 指定されたサイズのメモリを確保します。
         *
         * @param size 確保するメモリサイズ
         * @return 確保したメモリへのポインタ
         */
        void *raw_malloc(std::size_t size)
        {
            return ::malloc(size);
        }

        /**
         * 指定されたサイズのメモリを確保します。
         *
         * @param alignement アライメント
         * @param size 確保するメモリサイズ
         * @return 確保したメモリへのポインタ
         */
        void *raw_aligned_alloc(std::size_t alignment, std::size_t size)
        {
#if (IS_WINDOWS)
            return ::_aligned_alloc(alignment, size);
#else
            return ::aligned_alloc(alignment, size);
#endif
        }

        /**
         * 指定されたサイズのメモリを確保します。
         *
         * @param nmemb 確保するメモリの要素数
         * @param size 要素のサイズ
         * @return 確保したメモリへのポインタ
         */
        void *raw_calloc(std::size_t nmemb, std::size_t size)
        {
            return ::calloc(nmemb, size);
        }

        /**
         * ポインタが示すメモリブロックのサイズを変更します。
         *
         * @param ptr ポインタ
         * @param size 確保するメモリのサイズ
         * @return 確保したメモリへのポインタ
         */
        void *raw_realloc(void *ptr, std::size_t size)
        {
            return ::realloc(ptr, size);
        }

        /**
         * 指定されたメモリを解放します。
         *
         * @param ptr 解放するメモリへのポインタ
         */
        void raw_free(void *ptr)
        {
            return ::free(ptr);
        }

    } // namespace MemoryManager

} // namespace j

////////////////////////////////////////////////////////////////////////
//
// new/delete オーバーライド
//

void *operator new(std::size_t size)
{
    void *p = j::allocate(0, size, j::MemoryMark::ALLOCATED_NEW,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    if (p == nullptr)
    {
        throw std::bad_alloc();
    }
    return p;
}

void *operator new(std::size_t size, std::align_val_t al)
{
    void *p = j::allocate(static_cast<std::size_t>(al), size, j::MemoryMark::ALLOCATED_NEW_ALIGNED,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    if (p == nullptr)
    {
        throw std::bad_alloc();
    }
    return p;
}

void *operator new(std::size_t size, const std::nothrow_t &) noexcept
{
    void *p = j::allocate(0, size, j::MemoryMark::ALLOCATED_NEW,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    return p;
}

void *operator new(std::size_t size, std::align_val_t al, const std::nothrow_t &) noexcept
{
    void *p = j::allocate(static_cast<std::size_t>(al), size, j::MemoryMark::ALLOCATED_NEW_ALIGNED,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    return p;
}

void *operator new[](std::size_t size)
{
    void *p = j::allocate(0, size, j::MemoryMark::ALLOCATED_NEW_ARRAY,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    if (p == nullptr)
    {
        throw std::bad_alloc();
    }
    return p;
}

void *operator new[](std::size_t size, std::align_val_t al)
{
    void *p = j::allocate(static_cast<std::size_t>(al), size, j::MemoryMark::ALLOCATED_NEW_ARRAY_ALIGNED,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    if (p == nullptr)
    {
        throw std::bad_alloc();
    }
    return p;
}

void *operator new[](std::size_t size, const std::nothrow_t &) noexcept
{
    void *p = j::allocate(0, size, j::MemoryMark::ALLOCATED_NEW_ARRAY,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    return p;
}

void *operator new[](std::size_t size, std::align_val_t al, const std::nothrow_t &) noexcept
{
    void *p = j::allocate(static_cast<std::size_t>(al), size, j::MemoryMark::ALLOCATED_NEW_ARRAY_ALIGNED,
                          j::MemoryManager::gFile,
                          j::MemoryManager::gFunc,
                          j::MemoryManager::gLine);
    return p;
}

void operator delete(void *p) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}
void operator delete(void *p, std::align_val_t) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW_ALIGNED,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}

void operator delete(void *p, std::size_t) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}

void operator delete(void *p, std::size_t, std::align_val_t) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW_ALIGNED,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}

void operator delete[](void *p) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW_ARRAY,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}

void operator delete[](void *p, std::align_val_t) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW_ARRAY_ALIGNED,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}

void operator delete[](void *p, std::size_t) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW_ARRAY,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}
void operator delete[](void *p, std::size_t, std::align_val_t) noexcept
{
    j::deallocate(p, j::MemoryMark::ALLOCATED_NEW_ARRAY_ALIGNED,
                  j::MemoryManager::gFile,
                  j::MemoryManager::gFunc,
                  j::MemoryManager::gLine);
}