C言語の構造体のキャスト

型変換ネタ




C言語である構造体aを拡張した構造体bを作る場面が出たとして、
そのときにキャストを使って、今までの構造体aを使っていた関数に構造体bを構造体aにキャストして渡せば処理できるといいなぁ、
と、ふと思い試してみたら、できるっぽい。
まぁ、Win32Apiでめっちゃキャスト使って、コントロールを取ってきたりしてたから、それぐらいは出来そうだとは思ったけど。


構造体bの変数に構造体aの変数を代入することはできないけど、
構造体aの変数のアドレスを取って、それを構造体bのポインタへキャストし、
その参照を、構造体bの変数に代入することはできるという。

struct abc   n = {1, 2, '3', NULL};
struct abcEx nex={0}; // 0で初期化
// 中略
nex = *((struct abcEx *) &n);


別の構造体へキャストしたポインタをインクリメントするのはやばい。添え字を使ってもだめ。
構造体の大きさが一致すれば問題ないけど、そもそもリスト構造にすれば解決できるし、可変リストにできるからむしろリスト構造にしたほうがry


上位から下位へのキャストはあまり問題ないけど、
下位から上位へのキャストは、参照できない領域にアクセスできてしまうので、
その辺は注意したい。エラーも出ない。もしかしたらやばい領域をいじってるかもしれない。


おまけ。
char型を4つ並べた配列を(4byteの)int型のポインタにキャストすると、
バイトオーダーがビッグエンディアンかリトルエンディアンかもわかる。

	char a[4]="\x00\x01\x02\x03";
	int *p = (int *)a;
	printf("%02X %02X %02X %02X\n",a[0],a[1],a[2],a[3]);
	printf("%02X %02X %02X %02X\n", *p>>24, *p>>16&0xFF, *p>>8&0xFF, *p&0xFF);

出力が同じならビックエンディアン、逆ならリトルエンディアン。
ほとんどはリトルエンディアンです。
こんなことしなくても、unionでできるんだけども。
http://ja.wikipedia.org/wiki/エンディアン



オチはない。
キャスト楽しかった。


最後に検証に使ったコード(mingw gcc4.4.0)

#include <stdio.h>

// struct abc
struct abc{
	int a;
	short b;
	char c;
	struct abc *next;
};

// struct abcに変数exを加えたstruct abcEx
struct abcEx{
	int a;
	short b;
	char c;
	struct abc *next;
	int ex;
};


void t1(void){
	struct abc   n = {1, 2, '3', NULL};
	struct abcEx nex={0}; // 0で初期化
	struct abcEx nex2={0}; // 0で初期化
	struct abc   *p   = (struct abc   *) &nex; // 下位のバージョンへキャスト
	struct abcEx *pex = (struct abcEx *) &n;   // 上位のバージョンへキャスト
	
//	nex = (struct abcEx)n; // これはできないが・・・
	nex = *((struct abcEx *) &n); // これはできる。
	// しかしstruct abcでは ex が存在しないので、exの値は未定義。
	// 構造体が参照ではなくちゃんとコピーをしている確認をするために、nex内容を一部書き換える。
	
	nex.c = '4';
	nex.ex= 5; // 未定義な変換をしたので、初期化をしてみる
	
	// pex->exはn.exを指すが、nを定義したstruct abcにはexが存在しないため、本来なら参照できない位置にあるはず。
	pex->ex = 100;
	
	printf("n   (%p): %d, %hd, %c, %p, \n", &n, n.a, n.b, n.c, n.next);
	printf("nex (%p): %d, %hd, %c, %p, %d\n", &nex, nex.a, nex.b, nex.c, nex.next, nex.ex);
	printf("p   (%p): %d, %hd, %c, %p, \n", p, p->a, p->b, p->c, p->next);
	printf("pex (%p): %d, %hd, %c, %p, %d\n", pex, pex->a, pex->b, pex->c, pex->next, pex->ex);
	printf("nex2(%p): %d, %hd, %c, %p, %d\n", &nex2, nex2.a, nex2.b, nex2.c, nex2.next, nex2.ex);
}

// 配列を上位の型で参照
void t2(void){
	struct abc a[3] = {{1, 1, '1', NULL}, {2, 2, '2', NULL}, {3, 3, '3', NULL} }; // 初期化
	struct abcEx *pex = (struct abcEx *)a; // 上位型へキャスト
	
	printf("pex (%p): %d, %hd, %c, %p, %d\n", pex, pex->a, pex->b, pex->c, pex->next, pex->ex); // pex->exの値が2
	pex->ex = 100; // この操作はa[1].aを書き換えている。
	printf("a[1](%p): %d, %hd, %c, %p, \n", &a[1], a[1].a, a[1].b, a[1].c, a[1].next);
}

void test(void(*p)(void), const char *str){
	printf("---%s START---------------\n", str);
	p();
	printf("---END---------------\n");
}

void main(void){
	printf("sizeof(struct abc)  : %d\n", sizeof(struct abc));
	printf("sizeof(struct abcEx): %d\n", sizeof(struct abcEx));
	test(t1, "test1");
	test(t2, "test2");
}

きたねえ!