はじめに
構造体のサイズとアライメントについて過去にハマッたことがあったので、まとめてみました。
構造体のサイズ
次のような構造体があった場合、ST_TESTのbyte数は1+4+1の6byteと思いきや、Windows 32bitの場合12byteになります。
これはメモリ上にメンバ変数の領域を配置する際に、自動的に領域サイズが調整された結果このようになります。
typedef struct test{ char hoge[1]; ULONG num; char fuga[1]; } ST_TEST;
バイト境界とアライメント
メモリ上に変数が格納される際に、バイト境界に合わせてバイト数が調整されることをアライメントと呼びます。Windows32bitの場合はバイト境界が8byteとなっていて、バイト境界をまたがないように、最小限の調整が入ります。
メモリ上に変数が取られるときは、アドレスが、ある決められた区切りに合わせられていないといけないのです。この数値を「バイト境界」と呼びます。構造体はこの決まりによって、適当にメンバ間に空間が入れられます。これを、構造体の「アライメント」と呼びます。構造体自体もそうですが、構造体のメンバ1つ1つも、バイト境界に合わせられていないといけません。
Visual Studioのメモリウインドウから上記の構造体を見てみると、変数hogeの後ろに3byte、変数fugaの後ろにそれぞれ3byte領域が追加されていることが分かります。このため、画面キャプチャのように変数fugaに4byteコピーしてもアクセスバイオレーションが発生しません。
メンバ変数の領域が増える訳ではない
アライメントが発生した場合でも、メンバ変数のサイズが増えている訳ではありません。
以下のようなコードで、メンバ変数のサイズを調べてみるとchar型の変数は1byteですし、ULONG型変数は4byteのままです。
printf("%d\n", sizeof(t)); //-> 12 printf("%d\n", sizeof(t.hoge)); //-> 1 printf("%d\n", sizeof(t.num)); //-> 4 printf("%d\n", sizeof(t.fuga)); //-> 1
アライメントが問題となるケース
構造体サイズがアライメントされることを考慮せずに、t.hogeの1byteずらしたところに、t.numが存在すると勘違いをして、以下のようなアドレス演算を行うと、想定と違うアドレスを指すことになります。
char *num2 = (char*)t.hoge + sizeof(t.hoge); //->1byteしか進まないのでt.numが正しくとれない //char *num2 = (char*)t.hoge + 4; ULONG num3 = *num2;
バイト境界サイズを調整する
バイト境界のサイズを開発者側で決めるには、以下のようにpragma pack(size)を指定します。
pragma pack(8)
あるいは、ソリューションのプロパティから「構造体メンバーのアライメント」を指定します。