/**
* @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;
}