/** * @file kc_memory.c * @brief メモリ管理モジュール * @copyright 2003 - 2023 Nomura Kei */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> // 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 #ifdef KC_MEMORY_ENABLED #undef KC_MEMORY_ENABLED #endif #include <kc_memory.h> #include <kc_memory_dump.h> #include "kc_memory_entry_inner.h" #include "kc_memory_listener_inner.h" #include <kc_lock_guard.h> //////////////////////////////////////////////////////////////////////////////// // // プロトタイプ宣言 // --- KcMemory // 確保中のメモリダンプは、 KcMemory_dump にて実施可能であり、 // メモリリーク情報をダンプする KcMemory_dump_leak は明示的に公開はしない。 void KcMemory_dump_leak(void); static bool KcMemory_print(const char *msg); // --- KcMemoryManager static bool KcMemoryManager_set_listener(KcMemoryListener *listener); static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); static void KcMemoryManager_free(void *ptr); static void KcMemoryManager_init(void); static void KcMemoryManager_init_nop(void); static bool KcMemoryManager_add(KcMemoryEntry *entry); static bool KcMemoryManager_remove(KcMemoryEntry *entry); static void *KcMemoryManager_allocate(size_t alignment, size_t size, KcMemoryMark mark, const char *file, const char *func, int line); static void *KcMemoryManager_reallocate(void *ptr, size_t size, KcMemoryMark mark, const char *file, const char *func, int line); static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, KcMemoryMark mark, const char *file, const char *func, int line); static void KcMemoryManager_deallocate(void *ptr); //////////////////////////////////////////////////////////////////////////////// // // 変数 // // --- 公開変数 static mtx_t KcMemoryManager_mutex; static KcMemoryManager KcMemoryManager_mmgr = { // --- 公開関数 --- .set_listener = KcMemoryManager_set_listener, .entries = KcMemoryManager_entries, .freeif = KcMemoryManager_freeif, .dump = KcMemoryManager_dump, .malloc = KcMemoryManager_malloc, .aligned_alloc = KcMemoryManager_aligned_alloc, .calloc = KcMemoryManager_calloc, .realloc = KcMemoryManager_realloc, .free = KcMemoryManager_free, // --- 内部関数 --- ._init = KcMemoryManager_init, ._add = KcMemoryManager_add, ._remove = KcMemoryManager_remove, ._allocate = KcMemoryManager_allocate, ._reallocate = KcMemoryManager_reallocate, ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, ._deallocate = KcMemoryManager_deallocate, // --- 変数 --- ._listener = { .allocate = KcMemoryListener_allocate, .free = KcMemoryListener_free, .error = KcMemoryListener_error}, ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, ._tmpbuf = {0}, ._mutex = &KcMemoryManager_mutex}; KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; // ============================================================================= // KcMemory // ============================================================================= static KcMemoryListener KcMemory_listener; /** * メモリ管理を開始します。 * * 本関数を実行することで、次の動作が実施されます。 * (1) メモリ確保失敗時に、エラー情報を出力します。 * (2) プログラム終了時に、解放されていないメモリを出力します。 * (3) detail が true の場合、メモリ確保/解放時に出力します。 * ※出力先は、いずれも stderr となります。 * * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) */ void KcMemory_start(bool detail) { kc_memory_manager->_init(); static bool KcMemory_start = false; if (!KcMemory_start) { // atexit への登録は一度だけとする。 atexit(KcMemory_dump_leak); KcMemory_start = true; } if (detail) { KcMemory_listener.allocate = KcMemoryListener_dump_allocate; KcMemory_listener.free = KcMemoryListener_dump_free; } else { KcMemory_listener.allocate = NULL; KcMemory_listener.free = NULL; } KcMemory_listener.error = KcMemoryListener_dump_error; kc_memory_manager->set_listener(&KcMemory_listener); } /** * 現在確保されているメモリ情報を出力します。 */ void KcMemory_dump(void) { kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); } /** * メモリリークしているメモリ情報をダンプします。 */ void KcMemory_dump_leak(void) { if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) { fprintf(stderr, "##### Leak Memory #####\n"); KcMemory_dump(); } } /** * 指定されたメッセージを標準エラーに出力し、true を返します。 * * @param msg 標準エラーに出力するメッセージ * @return true (固定) */ static bool KcMemory_print(const char *msg) { fprintf(stderr, "%s", msg); return true; } // ============================================================================= // KcMemoryManager // ============================================================================= // ------------------------------------- // set_listener // ------------------------------------- /** * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 * * @param listener 登録するリスナ * @return true/false (リスナ登録成功/リスナ登録失敗) */ static bool KcMemoryManager_set_listener(KcMemoryListener *listener) { kc_memory_manager->_init(); kc_lock_guard(kc_memory_manager->_mutex) { // リスナ関数を設定。 // 関数が NULL の場合は、デフォルトの関数を設定する。 kc_memory_manager->_listener.allocate = (listener->allocate != NULL) ? listener->allocate : KcMemoryListener_allocate; kc_memory_manager->_listener.free = (listener->free != NULL) ? listener->free : KcMemoryListener_free; kc_memory_manager->_listener.error = (listener->error != NULL) ? listener->error : KcMemoryListener_error; } return true; } // ------------------------------------- // entries // ------------------------------------- /** * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 * ハンドラの戻り値が false の場合、呼び出しを終了します。 * * @param handler ハンドラ * @param info ハンドラの第二引数に渡される付加情報 * @return true/false (実行成功/実行失敗) */ static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) { kc_memory_manager->_init(); kc_lock_guard(kc_memory_manager->_mutex) { bool is_continue = true; for ( KcMemoryEntry *current = kc_memory_manager->_head._next; is_continue && (current != &(kc_memory_manager->_tail)); current = current->_next) { is_continue = handler(current, info); } } return true; } // ------------------------------------- // freeif // ------------------------------------- /** * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 * ハンドラの戻り値が true の場合、該当するメモリを解放します。 * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 * * @param handler ハンドラ * @param info ハンドラの第二引数に渡される付加情報 * @return true 固定 */ static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) { kc_memory_manager->_init(); kc_lock_guard(kc_memory_manager->_mutex) { bool is_free = false; for ( KcMemoryEntry *current = kc_memory_manager->_head._next; current != &(kc_memory_manager->_tail); /* NOP */ ) { is_free = handler(current, info); current = current->_next; if (is_free) { kc_memory_manager->free(current->_prev->data); } } } return true; } // ------------------------------------- // dump // ------------------------------------- /** * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 * * @param handler ハンドラ * @param bytes ダンプするバイト数 * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 * @param ascii true の場合、ASCII がダンプデータに追加されます。 * @param column カラム数 */ static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int column) { kc_memory_manager->_init(); kc_lock_guard(kc_memory_manager->_mutex) { bool is_continue = true; for ( KcMemoryEntry *current = kc_memory_manager->_head._next; is_continue && (current != &(kc_memory_manager->_tail)); current = current->_next) { is_continue = KcMemoryDump_dump( kc_memory_manager->_tmpbuf, KC_MEMORY_MAX_BUFFER_SIZE, current, bytes, binary, ascii, column); if (is_continue) { // エラーでなければハンドラを実行する。 is_continue = handler(kc_memory_manager->_tmpbuf); } } } } /** * 指定されたサイズのメモリを確保します。 * * @param size 確保するメモリサイズ * @param file メモリ確保ファイル名 * @psram func メモリ確保関数名 * @param line メモリ確保行番号 * @return 確保したメモリへのポインタ */ static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) { void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); return ptr; } /** * アライメント指定付きで、指定されたサイズのメモリを確保します。 * * @param alignment アライメント * @param size 確保するメモリサイズ * @param file メモリ確保ファイル名 * @param func メモリ確保関数名 * @param line メモリ確保行番号 * @return 確保したメモリへのポインタ */ static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) { void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED_ALIGNED, file, func, line); return ptr; } /** * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 * 確保したメモリ領域の内容は、0x00 で初期化されます。 * * @param nmemb 確保するメモリ要素数 * @param size 1要素あたりのメモリサイズ * @param file メモリ確保ファイル名 * @param func メモリ確保関数名 * @param line メモリ確保行番号 * @return 確保したメモリへのポインタ */ static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) { size_t n = nmemb * size; void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED_ALIGNED, file, func, line); if (ptr != NULL) { memset(ptr, 0x00, n); } return ptr; } /** * 指定されたポインタが指すメモリサイズを変更します。 * * @param ptr メモリサイズを変更するポインタ * @param size 変更後のメモリサイズ * @param file メモリ確保ファイル名 * @param func メモリ確保関数名 * @param line メモリ確保行番号 * @return 確保したメモリへのポインタ */ static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) { void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); return new_ptr; } /** * 指定されたメモリを解放します。 * * @param ptr 解放するメモリへのポインタ */ static void KcMemoryManager_free(void *ptr) { // malloc, calloc 等で確保されたメモリを解放する。 kc_memory_manager->_deallocate(ptr); } // ------------------------------------- // _init (初回目呼出し) // ------------------------------------- /** * [内部利用関数] * KcMemoryManager を初期化します。 * 内部で利用する mutex を初期化します。 */ static void KcMemoryManager_init(void) { // _init に初期化ダミー関数を設定し、 // 2回目以降本関数が Call されないようにする。 kc_memory_manager->_init = KcMemoryManager_init_nop; // mutex を初期化する。 int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); if (result != thrd_success) { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 perror("kc_memory : can't init mutex"); kc_memory_manager->_mutex = NULL; } } // ------------------------------------- // _init (2回目呼び出し以降) // ------------------------------------- /** * KcMemoryManager の初期化ダミー関数。 * _init の2回目以降の実行は、本関数が Call されます。 */ static void KcMemoryManager_init_nop(void) { // NOP } // ------------------------------------- // _add // ------------------------------------- /** * [内部利用関数] * 指定されたメモリエントリをメモリ管理に追加します。 * * @param entry 追加するメモリエントリ * @return true/false (追加実施/追加失敗) */ static bool KcMemoryManager_add(KcMemoryEntry *entry) { kc_memory_manager->_init(); kc_lock_guard(kc_memory_manager->_mutex) { // [tail] の 1つ前に挿入する entry->_next = &(kc_memory_manager->_tail); entry->_prev = kc_memory_manager->_tail._prev; kc_memory_manager->_tail._prev->_next = entry; kc_memory_manager->_tail._prev = entry; } return true; } // ------------------------------------- // _remove // ------------------------------------- /** * [内部利用関数] * 指定されたメモリエントリをメモリ管理より削除します。 * * @param entry 削除するメモリエントリ * @return true/false (削除実施/削除失敗) */ static bool KcMemoryManager_remove(KcMemoryEntry *entry) { kc_memory_manager->_init(); kc_lock_guard(kc_memory_manager->_mutex) { // entry の前後を直接リンクさせる entry->_prev->_next = entry->_next; entry->_next->_prev = entry->_prev; } return true; } // ------------------------------------- // _allocate // ------------------------------------- /** * [内部利用関数] * 指定されたサイズのメモリを確保します。 * 確保に失敗した場合、NULL を返します。 * alignment > 0 の場合、アライメント指定でメモリを確保します。 * * @param alignment アライメント * @param size 確保するメモリサイズ * @param file メモリ確保ファイル名 * @param func メモリ確保関数名 * @param line メモリ確保行番号 * @return 確保したメモリへのポインタ */ static void *KcMemoryManager_allocate(size_t alignment, size_t size, KcMemoryMark mark, const char *file, const char *func, int line) { void *data_ptr = NULL; KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); if (entry != NULL) { // メモリ確保成功 kc_memory_manager->_add(entry); kc_memory_manager->_listener.allocate(entry); data_ptr = entry->data; } else { // メモリ確保失敗 => エラー通知する。 KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); } return data_ptr; } // ------------------------------------- // _reallocate // ------------------------------------- /** * [内部利用関数] * 指定された ptr のメモリサイズを変更します。 * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 * 確保に失敗した場合、NULL を返します。 * * @param ptr ポインタ * @param size 確保するメモリサイズ * @param file メモリ確保ファイル名 * @param func メモリ確保関数名 * @param line メモリ確保行番号 * @return 確保したメモリへのポインタ */ static void *KcMemoryManager_reallocate(void *ptr, size_t size, KcMemoryMark mark, const char *file, const char *func, int line) { if (ptr == NULL) { return kc_memory_manager->_allocate(0, size, mark, file, func, line); } void *data_ptr = NULL; KcMemoryEntry *entry = (KcMemoryEntry *)ptr; entry--; if (entry->mark == KC_MEMORY_ALLOCATED) { data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); } return data_ptr; } // ------------------------------------- // _reallocate_managed_ptr // ------------------------------------- /** * [内部利用関数] * 管理されたメモリ領域に対する realloc を実施します。 * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 * * @param ptr ポインタ * @param size 確保するメモリサイズ * @param file メモリ確保ファイル名 * @param func メモリ確保関数名 * @param line メモリ確保行番号 * @return 確保したメモリへのポインタ */ static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, KcMemoryMark mark, const char *file, const char *func, int line) { KcMemoryEntry *entry = (KcMemoryEntry *)ptr; entry--; // (A) 一旦メモリを管理から外す。 kc_memory_manager->_remove(entry); void *data_ptr = NULL; KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); if (new_entry != NULL) { // メモリ確保成功 kc_memory_manager->_listener.free(new_entry); kc_memory_manager->_add(new_entry); kc_memory_manager->_listener.allocate(new_entry); data_ptr = new_entry->data; } else { // メモリ確保失敗 => エラー通知する。 KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 kc_memory_manager->_add(entry); } return data_ptr; } // ------------------------------------- // _deallocate // ------------------------------------- /** * [内部利用関数] * 指定されたポインタのメモリを解放します。 * * @param ptr 解放するメモリへのポインタ */ static void KcMemoryManager_deallocate(void *ptr) { if (ptr == NULL) { return; } KcMemoryEntry *entry = (KcMemoryEntry *)ptr; entry--; if (KC_MEMORY_IS_MANAGED(entry->mark)) { // 管理メモリの場合、エントリ解放する。 kc_memory_manager->_listener.free(entry); kc_memory_manager->_remove(entry); KcMemoryEntry_delete(entry); } else { // 、管理外メモリのため stdlib.h の free にて解放する。 raw_free(ptr); } } /** * stdlib.h の malloc。 * * @param size 確保するメモリサイズ * @return メモリへのポインタ */ void *raw_malloc(size_t size) { return malloc(size); } /** * stdlib.h の aligned_alloc。 * * @param alignment アライメント * @param size 確保するメモリサイズ * @return メモリへのポインタ */ void *raw_aligned_alloc(size_t alignment, size_t size) { #if (KC_IS_WINDOWS) return _aligned_malloc(size, alignment); #else return aligned_alloc(alignment, size); #endif } /** * stdlib.h の calloc。 * * @param nmemb 個数 * @param size 要素のサイズ * @return メモリへのポインタ */ void *raw_calloc(size_t nmemb, size_t size) { return calloc(nmemb, size); } /** * stdlib.h の realloc。 * * @param ptr メモリを再確保するポインタ * @param size 確保しなおすメモリサイズ * @return メモリへのポインタ */ void *raw_realloc(void *ptr, size_t size) { return realloc(ptr, size); } /** * stdlib.h の free * * @param ptr 解放するメモリへのポインタ */ void raw_free(void *ptr) { free(ptr); }