Version 12.11
パディングとアライメント
「前回は複数データの書き込みと読み込みをしてみました」
『 fread() と fwrite() 使ってね』
「でも、データひとつひとつを書き込むっていうのはちょっと面倒、という
わけで今回は構造体を使ってみます」
『あ、構造体丸ごと書き込んだりすれば簡単だもんね』
「そういうこと。でもそこで注意しなきゃいけない点があるから」
『注意点?』
「まずはファイルは抜かして考えようか。 Version 12.01 ( No.224 ) を思
い出して」
『ん……あ、そういえば構造体のサイズってやったね』
「そう。たとえば」
struct TEST_STRUCT
{
char m_ch;
int m_i;
};
「って構造体があるとします。このサイズは?」
『 1 + 4 = 5 ……じゃないんだよね』
「そう、答えは 8 」
void SizeOf_TEST_STRUCT()
{
TRACE( "%d\n", sizeof( TEST_STRUCT ) );
// 8
}
『謎よねー』
「ではもうひとつ」
struct TEST_STRUCT_DOUBLE
{
char m_ch;
double m_d;
};
「の構造体」
『 int じゃなくて double になってる』
「このサイズは、というと」
void SizeOf_TEST_STRUCT_DOUBLE()
{
TRACE( "%d\n", sizeof( TEST_STRUCT_DOUBLE ) );
// 16
}
『じゅ、 16 !? なんでそんなに大きいの!? だって、 double って 8
バイト……あ!』
「はい火美ちゃん」
『もしかして、 double の倍のサイズになってる!? さっきのは int の
倍だし』
「その通り! と、もうひとつ例を見ておこうか」
struct TEST_STRUCT_DOUBLE_2
{
char m_ch;
double m_d;
char m_ch2;
};
『後ろに char が増えた』
「これのサイズはというと」
void SizeOf_TEST_STRUCT_DOUBLE_2()
{
TRACE( "%d\n", sizeof( TEST_STRUCT_DOUBLE_2 ) );
// 24
}
『あ”、 24 、ってことは double の3倍?』
「そういうこと」
『……つまり、変数が3つあるから、3倍?』
「ほとんどそうだけど、ちょっと違うかな。正確に言うと、それぞれの変数
に【無駄な部分】が加えられて double と同じサイズになってる、ってとこ
ろかな」
『無駄な部分?』
「この構造体をメモリ上で見てみるとわかるかな」
void Use_TEST_STRUCT_DOUBLE_2()
{
TEST_STRUCT_DOUBLE_2 stTEST_STRUCT_DOUBLE_2;
TRACE( "%X\n", &stTEST_STRUCT_DOUBLE_2 );
// まずは 0xAA で初期化。
memset
( &stTEST_STRUCT_DOUBLE_2
, 0xAA
, sizeof( stTEST_STRUCT_DOUBLE_2 )
);
// 各変数をセット。
stTEST_STRUCT_DOUBLE_2.m_ch = 63;
stTEST_STRUCT_DOUBLE_2.m_d = 2.0;
stTEST_STRUCT_DOUBLE_2.m_ch2 = 63;
}
『 memset() って? ランタイムっぽいけど』
「 memset() は、第1引数のアドレスから第3引数バイトまで、第2引数で
埋めるっていうランタイム。 TRACE() で構造体のアドレスを出してるから
それでメモリウィンドウを見てみて」
『ほい。ん? どこで止めて?』
「最初は」
stTEST_STRUCT_DOUBLE_2.m_ch = 63;
「で止めて、 memset() の結果を見た方がいいかな」
『ほい。お、全部 AA だ』
「 8 バイト単位で改行するとこうなります」
AA AA AA AA AA AA AA AA
AA AA AA AA AA AA AA AA
AA AA AA AA AA AA AA AA
「今度は、最後の } の所まで進めて」
『構造体にデータ入れ終わる所までね。ほい』
3F AA AA AA AA AA AA AA
00 00 00 00 00 00 00 40
3F AA AA AA AA AA AA AA
『だいぶ変わった』
「まず真ん中の行から見て。これは double だから 8 バイトまるまる使い
ます」
『だから AA がないんだね』
「一方、上下の char は 1 バイトしか使わないので、先頭の 1 バイトにし
かデータが入っていません」
『残りは AA のままなんだ』
「つまり、 char の変数として使っている後ろに、 7 バイトの無駄な領域
があるってこと。この無駄な領域を【パディング】って言います」
『パディング?』
「そう、 padding 、英語で〈詰め物〉って意味」
『なるほど。でもさ、これってホント、無駄じゃん。なんでこんなふうに
なってるの?』
「それは、この方がコンピューターにとっては見やすいから」
『見やすい?』
「 Version 4.08 ( No.058 ) でちょっと触れたけど、 int みたいに、コン
ピューターが処理しやすいサイズがあるんです」
『あ、32ビットだから 4 バイトだと処理しやすい、とかゆーあれね』
「そう。今はひとつの変数が最大 8 バイト単位になるようになっていて、
そうするとコンピューターは 4 の倍数だから処理が楽になるんです」
『そのための無駄ってわけね』
「この、サイズを揃えるっていう仕組みを【アライメント】って言います」
『あらいめんと?』
「 alignment 、〈整列〉って意味」
『確かに並べてるね』
「このアライメントの仕組みがあるから、構造体を作ると自動的にパディン
グが入れられて、サイズが倍数になるんです」
『質問!』
「はい火美ちゃん」
『さっき〈今はひとつの変数が最大 8 バイト単位に〉って言ってたってこ
とは、それって変えられるってこと?』
「そういうこと。これは【プロジェクトの設定】>【C/C++】
>【コード生成】ページの【構造体メンバのアライメント】ってところに」
『 8 バイトって書いてある!』
「これを 1 バイトにしてリビルドして、もう一度試してみて」
『ほい』
「まずサイズを見てみます」
TRACE( "%d\n", sizeof( TEST_STRUCT_DOUBLE_2 ) );
// 10
『 10 ! char + double + char = 1 + 8 + 1 = 10 ?』
「そういうこと。メモリを見てみると、 memset() の直後は」
AA
AA AA AA AA AA AA AA AA
AA
「という感じになってます。その次にデータを入れてみると」
3F
00 00 00 00 00 00 00 40
3F
『うわー、パディングがまるまるなくなってる』
「このように、プロジェクトの設定でアライメントを変えると、構造体の中
身は大きく変わります」
『ここまで変わるんだ……質問!』
「はい火美ちゃん」
『結局、アライメントっていくつがいいの? デフォルトの 8 でいいの?』
「今はコンピューターの性能がかなりいいから、それほど大きな問題じゃな
いかも。 8 でもいいし 1 でもいいし。ただ」
『ただ?』
「今は CPU に比べて、メモリサイズやネットワークの太さのの方が問題に
なることが多いと思うから、そう考えるとパディング 1 の方がいいかも」
『 CPU はどんどん性能上がってるけどメモリとかまだ高いものね……』
/*
Preview Next Story!
*/
『でも今回ってバイナリーとかあんま関係なくない?』
「実は、ファイルの読み書きが絡んでくると重要になるんです」
『?』
「アライメントを 8 で書き込んで 1 で読み込んだら?」
『あ……』
「というわけで次回」
< Version 12.12 構造体のファイル読み書き >
『につづく!』
「この辺はうっかりバグになりやすいから」
『んーでもホントにうっかりって気が……。』