構造体を使ったテクニック②
構造体は便利ですが面倒と感じるのが初期化や、メンバ一つ一つへの代入だと思います。ここではすべてのメンバが同じ型である場合に限定して使えるTIPSを紹介します。
「構造体を配列にして使う」
typedef struct StData_{ unsigned int x; unsigned int y; unsigned int z; unsigned int w; }StData; StData stData; unsigned int *iBob = &(stData.x); iBob[0] = 6; iBob[1] = 9; iBob[2] = 8; iBob[3] = 2; printf("stData.x = %d \n", stData.x); printf("stData.y = %d \n", stData.y); printf("stData.z = %d \n", stData.z); printf("stData.w = %d \n", stData.w);
上のプログラムは先頭アドレスをポインタにもらうだけですが、すべて同じ型であれば、その後のメンバを配列としてつかえます。このプログラムの出力は
stData.y = 9
stData.z = 8
stData.w = 2
になります。for文で順次値を入れる時など上記のようにすると、構造体でも配列の特性が使えるようになります。この逆のパターンを見てみましょう。
「配列を構造体にして使う」
StData stData; unsigned int *iBob = &(stData.x); unsigned int iAlice[4]; iBob[0] = 6; iBob[1] = 9; iBob[2] = 8; iBob[3] = 2; *( (StData *)iAlice) = *( (StData *)iBob); //下記のように初期化してもAliceにコピーされている。 memset(iBob, 0, sizeof(stData)); printf("iAlice[0] = %d \n", iAlice[0]); printf("iAlice[1] = %d \n", iAlice[1]); printf("iAlice[2] = %d \n", iAlice[2]); printf("iAlice[3] = %d \n", iAlice[3]);
上のプログラムは配列を構造体にキャストしています。そうするとfor文でしかできないはずの配列まるごとコピーが出来てしまいます。このプログラムの出力は
iAlice[1] = 9
iAlice[2] = 8
iAlice[3] = 2
となります。ようするに配列と構造体を相互にいいとこどりができる訳です。ただ特に後者のプログラムはあまり良い使用方法ではありません。コンパイラによっては左辺のキャストは許していないからです。ただ出来ないといわれている事も、工夫をすれば出来るという事をお見せしたかったのです。なお、今回のようなデータのやりとりが必要な場合は初期設計が怪しいと考えた方が良いかと思います。レガシーコードが構造体になっているが、配列的な特性を使った方が引き回しが良い時などに前者のテクニックを使用します。最後に補足ですがプログラム途中でのstructの初期化にmemsetを使用しました。memsetはアクセス単位がbyteなので本当の初期化という意味ではメンバの型を意識した初期化を心掛けるべきです。またここまでの例はすべてstructのメンバの型が同じ型でした。普通はメンバの型はまちまちであり、その場合にはパディングが発生します。パディングを決めるアライメントはシステムによって異なりますのでstructのメンバを参照しないで中身をいじる場合は毎回注意が必要です。アライメントについてはVisual Studioにpragmaでunpackというのがありますので、それで調べてみるとよいと思います。