Newer
Older
project / modules / libsc / src / sc_memory.c
Nomura Kei on 26 Sep 2022 18 KB UPDATE
/**
 * @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;
}