/** * @file sc_memory.c * @bried メモリ管理モジュール * @author Nomura Kei * @copyright 2003 - 2022 Nomura Kei */ #include <stdio.h> #include <stdbool.h> #include <errno.h> #include <string.h> // ここでは常に本来の malloc, free を利用するため SC_MEMORY_MANAGE を無効化する。 #ifdef SC_MEMORY_MANAGE #undef SC_MEMORY_MANAGE #endif #ifndef SC_MEMORY_DUMP_LEAK #define SC_MEMORY_DUMP_LEAK (0) #endif // #include <sc.h> #include <sc_threads.h> #include <sc_memory.h> //////////////////////////////////////////////////////////////////////////////// // // 定数定義 // // 管理領域確保時のパディング #define PADDING (sizeof(void*) * 2) #define TO_ASCII(c) (((0x20 <= c) && (c < 0x7F)) ? c : '.'); /** 16進数文字 */ static const char HEX_CHARS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; //////////////////////////////////////////////////////////////////////////////// // // 変数定義 // // 外部公開変数 void (*sc_memory_ahandler)(sc_memory* meminfo, const char* msg) = NULL; void (*sc_memory_fhandler)(sc_memory* meminfo, const char* msg) = NULL; void (*sc_memory_ehandler)(sc_memory* meminfo, const char* msg) = NULL; // 以下、内部専用 static sc_memory sc_memory_head; static sc_memory sc_memory_tail; static sc_memory sc_memory_error; static thread_local bool sc_memory_nolock = false; static once_flag sc_memory_once_flag = ONCE_FLAG_INIT; static mtx_t sc_memory_mutex; /** メモリの種別マークに対応する文字列リスト */ static const char* SC_MEMORY_MARK_STRINGS[] = { "deleted ", "allocated ", "allocated(new) ", "allocated(new[])" }; //////////////////////////////////////////////////////////////////////////////// // // プロトタイプ宣言 (内部関数) // static void sc_memory_set_entry(sc_memory* entry, sc_memory_mark mark, size_t size, const char* file, int line, const char* func); static void sc_memory_do_init(void); static void sc_memory_init(void); static bool sc_memory_add(sc_memory* entry); static bool sc_memory_remove(sc_memory* entry); static void sc_memory_dump_data(char* buf, size_t buflen, void* data, size_t size); static void sc_memory_dump_data_ascii(char* buf, size_t buflen, void* data, size_t size); static void sc_memory_dump_leak(void); static void sc_memory_exec_ahandler(sc_memory* entry, const char* msg); static void sc_memory_exec_fhandler(sc_memory* entry, const char* msg); static void sc_memory_exec_ehandler(sc_memory* entry, const char* msg); // // スレッド対応用関数 // static bool sc_memory_mtx_lock(sc_memory* entry); static bool sc_memory_mtx_unlock(sc_memory* entry); //////////////////////////////////////////////////////////////////////////////// // // 公開関数実装 (本来の関数) // /** * 本来の malloc を実行します。 * * @param size 確保するメモリサイズ * @return 割り当てられたメモリに対するポインタ */ void* sc_raw_malloc(size_t size) { return malloc(size); } /** * 本来の calloc を実行します。 * * @param nmemb 確保するメモリの要素数 * @param size 1つのメモリ要求サイズ * @return 割り当てられたメモリに対するポインタ */ void* sc_raw_calloc(size_t nmemb, size_t size) { return calloc(nmemb, size); } /** * 本来の realloc を実行します。 * * @param ptr メモリブロックサイズを変更するポインタ * @param size 変更するサイズ * @return 再割り当てされたメモリに対するポインタ */ void* sc_raw_realloc(void* ptr, size_t size) { return realloc(ptr, size); } /** * 本来の free を実行します。 * * @param ptr 解放するメモリへのポインタ */ void sc_raw_free(void* ptr) { free(ptr); } //////////////////////////////////////////////////////////////////////////////// // // 公開関数実装 // /** * 指定されたサイズのメモリを確保します。 * * @param size 確保するメモリサイズ * @param file 呼び出し元ソースファイル * @param line 呼び出し元ソース行番号 * @param func 呼び出し元関数 * @return 確保したメモリへのポインタ */ void* sc_malloc(size_t size, const char* file, int line, const char* func) { void* ptr = sc_memory_allocate(NULL, size, SC_MEMORY_ALLOCATED, file, line, func); return ptr; } /** * 指定されたメモリサイズのメモリを確保します。 * 確保したメモリの中身は 0x00 でクリアされます。 * * @param nmemb 確保するメモリの要素数 * @param size 1つのメモリ要求サイズ * @param file 呼び出し元ソースファイル * @param line 呼び出し元ソース行番号 * @param func 呼び出し元関数 * @return 確保したメモリへのポインタ */ void* sc_calloc(size_t nmemb, size_t size, const char* file, int line, const char* func) { size_t n = nmemb * size; void* ptr = sc_memory_allocate(NULL, n, SC_MEMORY_ALLOCATED, file, line, func); if (ptr != NULL) { memset(ptr, 0x00, n); } return ptr; } /** * ポインタが示すメモリブロックのサイズを変更します。 * * @param ptr ポインタ * @param size 確保するメモリのサイズ * @param file 呼び出し元ソースファイル * @param line 呼び出し元ソース行番号 * @param func 呼び出し元関数 * @return 確保したメモリへのポインタ */ void* sc_realloc(void* ptr, size_t size, const char* file, int line, const char* func) { void* nptr = sc_memory_allocate(ptr, size, SC_MEMORY_ALLOCATED, file, line, func); return nptr; } /** * 指定されたメモリを解放します。 * * @param ptr 解放するメモリへのポインタ */ void sc_free(void* ptr) { sc_memory_free(ptr); } /** * 指定された callback に現在管理しているメモリが順次渡されます。 * callback 内で malloc, free などのメモリ操作は実施不可です。 * * @param callback コールバック関数 * @return true/false (処理成功/ロック獲得失敗) */ bool sc_memory_entries(void (*callback)(sc_memory* entry)) { sc_memory_init(); // ■ ----- LOCK ----- bool result = sc_memory_mtx_lock(NULL); if (!result) { return false; } sc_memory_nolock = true; // ロック無効 sc_memory* entry = sc_memory_head._next; while (entry != &sc_memory_tail) { callback(entry); entry = entry->_next; } sc_memory_nolock = false; // ロック無効解除 // ■ ----- UNLOCK ----- result = sc_memory_mtx_unlock(NULL); return result; } /** * callbaack が true を返すメモリを解放します。 * * @param entry メモリエントリ * @return ture/false (処理成功/ロック獲得失敗) */ bool sc_memory_freeif(bool (*callback)(sc_memory* entry)) { sc_memory_init(); // ■ ----- LOCK ----- bool result = sc_memory_mtx_lock(NULL); if (!result) { return false; } sc_memory_nolock = true; // ロック無効 sc_memory* entry = sc_memory_head._next; while (entry != &sc_memory_tail) { bool execfree = callback(entry); entry = entry->_next; if (execfree) { sc_free(entry->_prev); } } sc_memory_nolock = false; // ロック無効解除 // ■ ----- UNLOCK ----- result = sc_memory_mtx_unlock(NULL); return result; } /** * 指定されたメモリの情報をダンプします。 * * ``` * 使用例) * sc_memory_entries(sc_memory_dump_entry); * ``` * * @param entry メモリエントリ */ void sc_memory_dump_entry(sc_memory* entry) { printf("%-15s:%05d:%-15s (%5d) %s", entry->file, entry->line, entry->func, entry->size, sc_memory_markstr(entry->_mark)); // 16進数ダンプ出力 char buf[8 * 3]; sc_memory_dump_data(buf, sizeof(buf), entry->data, entry->size); printf(" %s", buf); // ASCII 出力 sc_memory_dump_data_ascii(buf, (sizeof(buf) / 3), entry->data, entry->size); printf(" | %s\n", buf); } /** * realloc(ptr, size) と同様の方法でメモリを確保します。 * * @param ptr メモリサイズを変更するメモリへのポインタ * @param size 確保するメモリサイズ * @param mark メモリ確保情報を示すマーク * @param file 呼び出し元ソースファイル * @param line 呼び出し元ソース行番号 * @param func 呼び出し元関数 * @return 確保したメモリへのポインタ */ void* sc_memory_allocate(void* ptr, size_t size, sc_memory_mark mark, const char* file, int line, const char* func) { if (size == 0) { // size == 0 の場合は、free と等価 sc_memory_free(ptr); return NULL; } sc_memory* entry = (sc_memory*) sc_raw_malloc(size + sizeof(sc_memory) + PADDING); if (entry == NULL) { // メモリ確保失敗 errno = ENOMEM; sc_memory_set_entry(&sc_memory_error, mark, size, file, line, func); sc_memory_exec_ehandler(&sc_memory_error, "allocate error"); return NULL; } if (ptr != NULL) { sc_memory* old_entry = (sc_memory*) ptr; switch (old_entry->_mark) { case SC_MEMORY_DELETED: // 削除済みメモリに対しての realloc case SC_MEMORY_ALLOCATED_NEW: // new で確保したメモリに対しての realloc case SC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保したメモリに対しての realloc // 管理メモリ (エラー) errno = EINVAL; sc_memory_exec_ehandler(old_entry, "realloc error"); return NULL; case SC_MEMORY_ALLOCATED: default: // ptr 領域のデータを移動し、解放する。 memmove((entry + 1), ptr, size); sc_memory_free(ptr); break; } } // 基本設定 sc_memory_set_entry(entry, mark, size, file, line, func); (void) sc_memory_add(entry); return (entry->data); } /** * 指定されたポインタの指すメモリ領域を解放します。 * * @param ptr 解放するメモリへのポインタ */ void sc_memory_free(void* ptr) { if (ptr == NULL) { // NULL ポインタに対しては何もしない。 return; } sc_memory* entry = (sc_memory*) ptr; entry--; switch (entry->_mark) { case SC_MEMORY_ALLOCATED: // 管理メモリ (void) sc_memory_remove(entry); entry->_mark = SC_MEMORY_DELETED; entry->size = 0; sc_raw_free(entry); break; case SC_MEMORY_DELETED: // 削除済みメモリに対しての free case SC_MEMORY_ALLOCATED_NEW: // new で確保したメモリに対しての free case SC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保したメモリに対しての free break; default: // 管理外メモリ sc_raw_free(ptr); break; } } /** * 指定されたメモリ種別マークの文字列表現を返します。 * * @param mark 種別マーク * @return 指定された種別マークの文字列表現 */ const char* sc_memory_markstr(sc_memory_mark mark) { if (sc_memory_is_valid(mark)) { // 管理されている種別マークに対応する文字列を返す。 return SC_MEMORY_MARK_STRINGS[(mark & 0x03)]; } return "unmanaged "; } //////////////////////////////////////////////////////////////////////////////// // // 内部関数 // /** * 指定された entry に、各値を設定します。 * * @param entry エントリ * @param mark 種別マーク * @param size メモリサイズ * @param file 呼び出し元ソースファイル * @param line 呼び出し元ソース行番号 * @param func 呼び出し元関数 */ static void sc_memory_set_entry(sc_memory* entry, sc_memory_mark mark, size_t size, const char* file, int line, const char* func) { entry->size = size; entry->file = file; entry->line = line; entry->func = func; entry->_mark = mark; entry->data = (entry + 1); entry->_prev = NULL; entry->_next = NULL; } /** * メモリ管理を初期化します。 * 本関数は、sc_memory_init より一度だけ実行されます。 */ static void sc_memory_do_init(void) { int is_success; // mutex (for メモリ管理) is_success = mtx_init(&sc_memory_mutex, mtx_plain); if (is_success != thrd_success) { perror("can't init mutex"); } sc_memory_set_entry(&sc_memory_head , SC_MEMORY_DELETED, 0, NULL, 0, NULL); sc_memory_set_entry(&sc_memory_tail , SC_MEMORY_DELETED, 0, NULL, 0, NULL); sc_memory_set_entry(&sc_memory_error, SC_MEMORY_DELETED, 0, NULL, 0, NULL); sc_memory_head._prev = sc_memory_head._next = &sc_memory_tail; sc_memory_tail._prev = sc_memory_tail._next = &sc_memory_head; if (SC_MEMORY_DUMP_LEAK != 0) { atexit(sc_memory_dump_leak); } } /** * 指定されたエントリを追加します。 * 通常 nolock は、false を指定します。 * * @param entry 追加するエントリ */ static bool sc_memory_add(sc_memory* entry) { sc_memory_init(); // ■ ----- LOCK ----- bool result = sc_memory_mtx_lock(entry); if (!result) { return false; } entry->_next = &sc_memory_tail; entry->_prev = sc_memory_tail._prev; sc_memory_tail._prev->_next = entry; sc_memory_tail._prev = entry; // ■ ----- UNLOCK ----- result = sc_memory_mtx_unlock(entry); sc_memory_exec_ahandler(entry, "allocate"); return result; } /** * 指定されたエントリを削除します。 * * @param entry エントリ */ static bool sc_memory_remove(sc_memory* entry) { // ■ ----- LOCK ----- bool result = sc_memory_mtx_lock(entry); if (!result) { return false; } entry->_prev->_next = entry->_next; entry->_next->_prev = entry->_prev; // ■ ----- UNLOCK ----- result = sc_memory_mtx_unlock(entry); sc_memory_exec_fhandler(entry, "free"); return result; } /** * 指定されたバッファにデータのダンプ情報を格納します。 * ダンプ情報は、バッファサイズ に併せて調整されます。 * * 例) 16 byte のダンプ情報を出力する場合は、バッファサイズ (16 * 3) を指定ください。 * バッファの最後には、'\0' が入ります。 * * @param buf ダンプデータ(文字列表現)を格納するバッファ * @param buflen バッファサイズ * @param data データへのポインタ * @param size データのサイズ */ static void sc_memory_dump_data(char* buf, size_t buflen, void* data, size_t size) { unsigned char* dptr = (unsigned char*) data; int len = buflen / 3; int dlen = ((int) size < len) ? (int) size : len; int idx = 0; for (; idx < dlen; idx++) { buf[(idx * 3) + 0] = HEX_CHARS[(*dptr >> 4) & 0x0F]; buf[(idx * 3) + 1] = HEX_CHARS[(*dptr ) & 0x0F]; buf[(idx * 3) + 2] = ' '; dptr++; } for (; idx < len; idx++) { buf[(idx * 3) + 0 ] = '-'; buf[(idx * 3) + 1 ] = '-'; buf[(idx * 3) + 2 ] = ' '; } buf[(idx - 1) * 3 + 2] = '\0'; } /** * 指定されたバッファにデータのASCII形式ダンプ情報を格納します。 * ダンプ情報は、バッファサイズ に併せて調整されます。 * * 例) 16 byte のダンプ情報を出力する場合は、バッファサイズ (16 + 1) を指定ください。 * バッファの最後には、'\0' が入ります。 * * @param buf ダンプデータ(文字列表現)を格納するバッファ * @param buflen バッファサイズ * @param data データへのポインタ * @param size データのサイズ */ static void sc_memory_dump_data_ascii(char* buf, size_t buflen, void* data, size_t size) { unsigned char* dptr = (unsigned char*) data; int len = buflen - 1; int dlen = ((int) size < len) ? (int) size : len; int idx = 0; for (; idx < dlen; idx++) { buf[idx] = TO_ASCII(*dptr); dptr++; } for (; idx < len; idx++) { buf[idx] = ' '; } buf[idx] = '\0'; } /** * メモリリーク情報をダンプします。 */ static void sc_memory_dump_leak(void) { sc_memory_entries(sc_memory_dump_entry); } //////////////////////////////////////////////////////////////////////////////// // // ハンドラ実行ラッパー関数 // /** * メモリ確保時のハンドラを実行します。 * ハンドラが未登録の場合は何もしません。 * * @param entry 確保したメモリエントリ * @param msg メッセージ */ static void sc_memory_exec_ahandler(sc_memory* entry, const char* msg) { if (sc_memory_ahandler != NULL) { sc_memory_ahandler(entry, msg); } } /** * メモリ解放時のハンドラを実行します。 * ハンドラが未登録の場合は何もしません。 * * @param entry 解放するメモリエントリ * @param msg メッセージ */ static void sc_memory_exec_fhandler(sc_memory* entry, const char* msg) { if (sc_memory_fhandler != NULL) { sc_memory_fhandler(entry, msg); } } /** * エラー発生時のハンドラを実行します。 * ハンドラが未登録の場合は何もしません。 * * @param entry 操作対象メモリエントリ * @param msg メッセージ */ static void sc_memory_exec_ehandler(sc_memory* entry, const char* msg) { if (sc_memory_ehandler != NULL) { sc_memory_ehandler(entry, msg); } } //////////////////////////////////////////////////////////////////////////////// // // Thread サポート // /** * メモリ管理を初期化します。 * 実際の初期化処理は、一度だけ実行される sc_memory_do_init にて実施されます。 */ static void sc_memory_init(void) { // sc_memory_do_init を1度だけ実行します。 call_once(&sc_memory_once_flag, sc_memory_do_init); } /** * mutex によるロックをかけます。 * ロックに成功した場合、true を返します。 * ロックに失敗した場合、sc_memory_ehandler を実行し、false を返します。 * * @param entry 操作対象メモリの情報 * @return true/false (ロック成功/ロック失敗) */ static bool sc_memory_mtx_lock(sc_memory* entry) { // ロック無効 (sc_memory_entries, sc_memory_freeif 実行中) の場合、何もせず true を返す。 if (sc_memory_nolock) { return true; } bool result = (mtx_lock(&sc_memory_mutex) == thrd_success); if (!result) { perror("mtx lock error"); sc_memory_exec_ehandler(entry, "mtx lock error"); } return result; } /** * mutex によるロックを解除します。 * ロック解除に成功した場合、true を返します。 * ロック解除に失敗した場合、sc_memory_ehandler を実行し、false を返します。 * * @param entry 操作対象メモリの情報 * @return true/false (ロック解除成功/ロック解除失敗) */ static bool sc_memory_mtx_unlock(sc_memory* entry) { // ロック無効 (sc_memory_entries, sc_memory_freeif 実行中) の場合、何もせず true を返す。 if (sc_memory_nolock) { return true; } bool result = (mtx_unlock(&sc_memory_mutex) == thrd_success); if (!result) { perror("mtx unlock error"); sc_memory_exec_ehandler(entry, "mtx unlock error"); } return result; }