可変長データを直接持つ構造体

構造体は、基本可変長データを持つときは、別に確保した可変長データを、アドレスとして格納するのが基本ですが、
ここで紹介する方法を使用することにより、一度のメモリ確保だけで済む方法を紹介してみます。


目次


一つの可変長データを直接持つ構造体の場合

先にコードを示すと、このようになります。


#include <stdio.h>
#include <stdlib.h>

typedef struct _Data {
	int		mIntData;
	int		mIntArrayData[1];
} Data;

int main( void ){
	Data*	data;
	int		i;

	// メモリ確保(10 個のデータを持つ配列を含んだ構造体データを確保)
	data = (Data*)malloc(sizeof(Data) - sizeof(int) + (sizeof(int) * 10));
	if(!data) return -1;

	data->mIntData = 0;

	// 確保した配列へのアクセス
	for(i = 0;i < 10;++i) {
		data->mIntArrayData[i] = i;
		data->mIntData += i;
	}

	// 代入した各データの値を表示
	printf("data->mIntData : %dn", data->mIntData);
	for(i = 0;i < 10;++i) {
		printf("data->mIntArrayData[%d] : %dn", i, data->mIntArrayData[i]);
	}

	// キー待ちさせて表示を確認
	getchar();

	// メモリ解放
	free(data);
	return 0;
}

Data 構造体の mIntArrayData[1] と、メモリ確保している処理の確保サイズ計算箇所がポイントとなります。
構造体上では、個数1の配列として定義されていますが、構造体の最後の位置に置かれているため、このような可変長データを確保・アクセスできます。

なぜかというと、答えは簡単です。

構造体の変数はメモリ上では定義順に配置されるようになっており、最後の変数の後には(アラインメントによる空白がある場合がありますが)何もありません。
つまり、単なる構造体のデータにそのサイズ以上のメモリを確保しても無意味ですが、無意味に確保した領域にアクセスしても、
確保されているデータのため、メモリアクセスエラーにはなりません。

そして、指定したサイズの配列を定義しても、プログラム上ではそのサイズ以上の場所にアクセスすることが出来ます。

この二つを利用し、

(構造体のサイズ-可変長データのサイズ+(可変長データのサイズ×配列数))

のサイズを確保することにより、
可変長データを直接持つ構造体を確保することが出来ます。

なお、”可変長データを直接持つ構造体” のサイズの計算は、下記のマクロによって簡略化できます。


#define MALLOC_VL(type, aryType, count) 
	((type*)malloc(sizeof(type) - sizeof(aryType) + (sizeof(aryType) * count)))

※ MALLOC_VL の VL は、「Variable Length」(可変長)の意味です。

ちなみに、2次元配列を直接持つ構造体にする場合は、構造体側を


typedef struct _Data {
	int		mIntData;
	int		mIntArrayData[1][1];
} Data;

とし、確保サイズの計算は、上記の計算式中の配列数を二次元分の個数にするだけです。
(3次元も同様の考え方で出来ます)


二つ以上の可変長データを直接持つ構造体の場合

上記の方法では一つの可変長データしか用意・アクセスできません。
しかし、「一つ目の可変長データの後に二つ目の可変長データを確保する」ことにより、可能になります。
(ただし、確保後の可変長データのサイズ変更は難しくなります)
各記述コードはそれぞれ下記のようなものになります。


// 構造体の定義
typedef struct _Data {
	int		mIntData;
	char*	mCharArrayData;		// char 型の配列へのポインタ。
	int		mIntArrayData[1];	// int 型の配列。
} Data;

// 確保マクロ
#define MALLOC_VL2(type, aryType1, aryType2, count1, count2) 
	((type*)malloc(sizeof(type) - sizeof(aryType1) + (sizeof(aryType1) * count1) + (sizeof(aryType2) * count2)))

// メモリ確保(それぞれ 10 個のデータを持つ配列を含んだ構造体データを確保)
data = MALLOC_VL2(Data, int, char, 10, 10);
// char 型の配列へのポインタアドレスの取得
data->mCharArrayData = (char*)(&amp;amp;amp;data->mIntArrayData[10]);

注意点として、 Data 構造体サイズから引くのは配列指定している変数のサイズだけです。
他の可変長データへのアクセスに使っている変数はアドレスを格納しているだけなので、
(実体のデータは構造体定義内には存在していない。実体は構造体外の確保した領域なので)
その変数のサイズも引いてしまうと、実際要求しているサイズより少なくなってしまいます。


“可変長データを直接持つ構造体” を可変数で直接持つ構造体の場合

タイトルだけではわかりにくいと思いますが、要は下記のような構造体を構成した場合です。


typedef struct _SubData {
	int		mCharCount;			// 配列サイズ(必須)
	char	mSubCharArray[1];	// char 型配列
} SubData;

typedef struct _MainData {
	int		mSubDataCount;			// SubData の個数(任意)
	SubData	mMainSubDataArray[1];	// SubData の配列
} MainData;

この構成の場合でも一括確保は可能です。
しかし、 MainData から SubData への配列によるアクセスは特殊な方法になるので注意してください。
確保サイズの計算は、上の構造体を例にとると、
(各 SubData の可変長データを含めた構造体サイズ+ MainData の配列領域以外の構造体サイズ)
となります。
各 SubData へのアクセス方法は、以下の計算で行えます。


SubData* GetSubData(MainData* data, int index){
	SubData* sub = data->mMainSubDataArray;
	while(index > 0){
		sub = (SubData*)((unsigned char*)sub + sizeof(SubData) - sizeof(char) + (sizeof(char) * sub->mCharCount));
		--index;
	}
	return sub;
}

要は、先頭のデータから各位置にある構造体のサイズずつポインタをずらして進めているだけです。
このため、頻繁にランダムアクセスをする場合、 SubData アクセス専用の配列を用意することで高速化する方法があります。

※ここではこれ以上の解説を割愛します。もしこれ以上の説明が必要であれば連絡を下さい。追筆します。


クラスでの応用と注意点

これらの構造はクラスでも応用は可能です。
ただし、継承されるクラスでは使用できません。
理由は、継承されるクラスには後ろに継承したクラスのデータが並ぶようになっているため、
可変長データがその領域を侵犯し、データを破壊してしまうためです。

制作物とか日記とかプログラミングTipsとか。