【C言語】可変長バッファを作成する

自動的に配列サイズを拡張する可変長バッファのサンプルプログラムです。
効率はそんなに良くありませんが、配列長を気にせずに済むので可変長のデータを手軽に扱う事ができます。

C言語ライブラリ「cmn-clib」に同じものがありますので、作るのがめんどくさい人はこちらを使用・もしくはソースをコピーしてください。「CmnDataBuffer_xxx」という名前で実装されています。

使い方イメージ

サンプルでは、CmnDataBufferという名前で可変長バッファを実装します。

Createで可変長バッファを作成して、Set(データの設定)・Append(データの追加)を行います。
Set・Appendによりメモリが足りなくなった場合は、自動的にreallocでメモリ(ヒープ領域)を拡張というか再割り当てしますので、煩雑なメモリ操作をしなくて済みます。

char d1[] = {'0','1','2','3','4','5','6','7','8','9'};
char d2[] = {'A','B','C'};
/* 可変長バッファの作成(初期サイズ0Byteで作成。最低限必要なバッファサイズが分かっている場合、サイズを指定しておくと効率的。) */
CmnDataBuffer *buf = CmnDataBuffer_Create(0);

/* 可変長バッファにデータをセット(既存データはクリアしてセット) */
CmnDataBuffer_Set(buf, d1, sizeof(d1));

/* データを追加(既存データの末尾に追加) */
CmnDataBuffer_Append(buf, d2, sizeof(d2));

/* データを取得 */
buf->data;  /* 0123456789ABC */
buf->size;  /* 13 */

/* 可変長バッファを破棄 */
CmnDataBuffer_Free(buf);

上記は簡単な例として文字配列を扱っていますが、CmnDataBufferはバイナリデータのバッファですので、終端文字(’\0’)の付与などは行いません。可変長の文字列を扱いたい場合は、下記のCmnDataString(CmnDataBufferの文字列版です)を併せて作っておくと便利です。

CmnDataBuffer の実装

データ構造

可変長バッファを管理する構造体「CmnDataBuffer」を準備します。

typedef struct _tag_CmnDataBuffer {
	void *data;			/**< バッファへのポインタ。Append/Setによる領域拡張時にアドレスが変わる可能性があるため、利用側で保存せず、常に最新のポインタを参照すること。 */
	size_t bufSize;		/**< バッファ領域のサイズ */
	size_t size;		/**< 有効なデータのサイズ */
} CmnDataBuffer;

関数

CmnDataBuffer_Create:可変長バッファの新規作成

可変長バッファを新規作成します。引数のbufSizeはバッファの初期サイズです。予め使用するバッファのサイズが何となく分かっている場合は指定しておくと無駄なreallocが起こらないので効率的になります。初期バッファサイズが小さくても自動的に拡張するので、なんとなくのサイズで大丈夫です。

尚、bufSizeに0(ゼロ)が指定された場合はデフォルトのバッファサイズを割り当てるようにします。(この例では4096byte)

DEFAULT_BUFFER_SIZEは、デフォルトの初期バッファサイズとAppend時の拡張サイズに使われる数値です。メモリに余裕のある環境や、大容量のメモリをよく使うプログラムでは大きめにした方が性能が良くなります。

static const size_t DEFAULT_BUFFER_SIZE = 4096;

/**
 * @brief 自動領域拡張バッファ作成
 *
 *  自動領域拡張バッファを新規に作成する。
 *
 * @param bufSize 初期バッファサイズ。0を指定した場合はデフォルトのバッファサイズが適用される。
 * @return 作成したバッファへのポインタ。作成に失敗した場合はNULLを返す。
 */
CmnDataBuffer* CmnDataBuffer_Create(size_t bufSize)
{
	CmnDataBuffer *ret;

	ret = malloc(sizeof(CmnDataBuffer));
	if (ret == NULL) {
		return NULL;
	}

	if (bufSize == 0) {
		bufSize = DEFAULT_BUFFER_SIZE;
	}
	if ((ret->data = malloc(bufSize)) == NULL) {
		return NULL;
	}
	ret->bufSize = bufSize;
	ret->size = 0;

	return ret;
}

CmnDataBuffer_Set:データの設定(既存データの上書き)

バッファにデータを設定する関数です。
元々データが設定された場合は、新しいデータに上書きされます。

データを設定する際、バッファサイズが足りなければ拡張子、バッファサイズが大き過ぎたら小さくします。バッファサイズの増減をするときは、必要サイズより少し大きめに調整します。
普通の使い方だと、Setした後にちょこちょこAppendしますが、Appendした時にメモリの再割り当てをする回数を減らすためです。

増減の幅は用途やお好みによって調整すると良いと思います。

/**
 * @brief 自動領域拡張バッファへのデータ設定
 *
 *  自動領域拡張バッファにデータを設定する。もとのデータは上書かれる。
 *
 * @param buf 自動拡張バッファ
 * @param data 設定するデータ
 * @param len バッファに書き込むデータの長さ
 * @return 正常:0, エラー:-1
 */
int CmnDataBuffer_Set(CmnDataBuffer *buf, const void *data, size_t len)
{
	void *buftmp;
	size_t oldBufSize = buf->bufSize;
	size_t newBufSize = len;
	int resize = False;

	/* 領域が不足する場合は拡張 */
	if (oldBufSize < newBufSize) {
		/* すでにある程度大きい領域を拡張する場合は少し多めに拡張する */
		if (DEFAULT_BUFFER_SIZE < oldBufSize) {
			newBufSize += (newBufSize / 10);
		}
		resize = True;
	}
	/* 領域が余る場合、3分の1以上の空きができるならバッファを3分の1縮小する */
	else if (newBufSize < oldBufSize && (oldBufSize / 3) < (oldBufSize - newBufSize)) {
		newBufSize = oldBufSize - (oldBufSize / 3);
		resize = True;
	}

	if (resize) {
		buftmp = realloc(buf->data, newBufSize);
		if (buftmp == NULL) {
			return -1;
		}
		buf->data = buftmp;
		buf->bufSize = newBufSize;
	}

	/* データ追加 */
	memcpy(buf->data, data, len);
	buf->size = len;

	return 0;
}

CmnDataBuffer_Append:データの追加(既存データの末尾に追加)

末尾にデータを追加します。データを追加する際にバッファサイズが足りなければ拡張します。

/**
 * @brief 自動領域拡張バッファへのデータ追加
 *
 *  自動領域拡張バッファの末尾にデータを追加する。
 *
 * @param buf 自動拡張バッファ
 * @param data 追加するデータ
 * @param len バッファに書き込むデータの長さ
 * @return 正常:0, エラー:-1
 */
int CmnDataBuffer_Append(CmnDataBuffer *buf, const void *data, size_t len)
{
	void *buftmp;
	size_t oldBufSize = buf->bufSize;
	size_t newBufSize = buf->size + len;

	/* 領域が不足する場合は拡張 */
	if (oldBufSize < newBufSize) {
		/* 領域がデフォルトバッファサイズの半分以上になったらデフォルトバッファサイズ単位に拡張する */
		if ((DEFAULT_BUFFER_SIZE / 2) < newBufSize) {
			newBufSize += DEFAULT_BUFFER_SIZE - (newBufSize % DEFAULT_BUFFER_SIZE);
		}

		buftmp = realloc(buf->data, newBufSize);
		if (buftmp == NULL) {
			return -1;
		}
		buf->data = buftmp;
		buf->bufSize = newBufSize;
	}

	/* データ追加 */
	memcpy(((char*)buf->data) + buf->size, data, len);
	buf->size += len;

	return 0;
}

CmnDataBuffer_Delete:データの削除(指定の長さ分、データの末尾を削除)

バッファ末尾のデータを指定したサイズ分削除します。

/**
 * @brief 自動領域拡張バッファのデータ削除
 *
 *  自動領域拡張バッファのデータを一部もしくは全部削除する。
 *
 * @param buf 自動拡張バッファ
 * @param len 削除する長さ
 */
void CmnDataBuffer_Delete(CmnDataBuffer *buf, size_t len)
{
	if (buf->size < len) {
		len = buf->size;
	}
	buf->size -=len;
}

※これ、手抜きですね。Setの時と同じようにバッファサイズも小さくしてやった方が、使わないメモリを解放してあげられるので良いと思います。

CmnDataBuffer_Free:バッファの破棄

バッファを破棄してメモリを解放します。

/**
 * @brief 自動領域拡張バッファの解放
 *
 *  自動領域拡張バッファが不要になった場合、メモリ解放のために必ず本関数を呼び出すこと。
 *
 * @param buf 自動拡張バッファ
 */
void CmnDataBuffer_Free(CmnDataBuffer *buf)
{
	free(buf->data);
	free(buf);
}

コメント

タイトルとURLをコピーしました