|
構造体のサイズとアライメント
構造体のサイズは、単純に構造体のメンバのサイズを合計したものにはならないときがあります。具体的な例を見てみましょう。
struct sample1_t
{
char cvalue;
long lvalue;
};
|
上の構造体のサイズは、メンバのサイズを足すと5バイトですが、Win32では(設定を変えなければ)8バイトになります。これはメモリ上に変数が取られるときの都合によるものです。
メモリ上に変数が取られるときは、アドレスが、ある決められた区切りに合わせられていないといけないのです。この数値を「バイト境界」と呼びます。構造体はこの決まりによって、適当にメンバ間に空間が入れられます。これを、構造体の「アライメント」と呼びます。構造体自体もそうですが、構造体のメンバ1つ1つも、バイト境界に合わせられていないといけません。
上の例の場合は、cvalueの直後に3バイトの空間が入れられます。この空間は「パディング」と呼ばれます。
バイト境界はOSやCPUによって変わります。ですので、プログラムを他のOSやCPUに移植する場合や、OS間でデータの共有などを考える場合は、構造体のアライメントを意識しないといけないことになります。
構造体のサイズはsizeof演算子で取得できますが、このサイズはアライメントによって変わってきます。逆に言うとsizeof演算子で返ってくるサイズは、アライメントが反映されているので、常に正しいと言えるでしょう。
また、次のようにcvalueを後にした場合でも、同じように、サイズは8バイトになります。これは構造体を配列にしたときに、バイト境界をまたがないようにしているからです。
struct sample2_t
{
long lvalue;
char cvalue;
};
|
では、バイト境界が変わると、構造体のサイズはどう変わるのでしょうか。上のsample2_tの場合は次のようになります。
| バイト境界 |
sample2_tサイズ |
パディング |
|
16
|
8
|
3
|
|
8
|
8
|
3
|
|
4
|
8
|
3
|
|
2
|
6
|
1
|
|
1
|
5
|
0
|
もう一つの例を見てみます。次の構造体で考えてみます。
struct sample3_t
{
double dvalue;
char cvalue;
};
|
double型は8バイトです。この構造体の場合は次のようになります。
| バイト境界 |
sample3_tサイズ |
パディング |
|
16
|
16
|
7
|
|
8
|
16
|
7
|
|
4
|
12
|
3
|
|
2
|
10
|
1
|
|
1
|
9
|
0
|
少し法則のようなものが見えてきますね。バイト境界の倍数になるようにパディングが入れられているようにも見えますが、バイト境界が16の時のsample2_tのサイズは8なので、ここだけ違います。より正確に言うなら、「配列にしたときにバイト境界をまたがないように、最小限のパディングが入れられる」と言えそうです。
構造体のサイズだけでなく、メンバ間のパディングがどう変わるかも見てみましょう。次のような構造体で考えてみます。"パディング"とコメントが書いてあるところにパディングが入れられます。単純なメンバのサイズの合計は17バイトですが、これがどのように増えるのでしょうか。
struct sample4_t
{
char cvalue1;
// パディング1 (dvalueのためのパディング)
double dvalue;
char cvalue2;
// パディング2 (lvalueのためのパディング)
long lvalue;
char cvalue3;
// パディング3 (svalueのためのパディング)
short svalue;
// パディング4 (sample4_tのためのパディング)
};
|
| バイト境界 |
sample4_tサイズ |
パディング1 |
パディング2 |
パディング3 |
パディング4 |
|
16
|
32
|
7
|
3
|
1
|
4
|
|
8
|
32
|
7
|
3
|
1
|
4
|
|
4
|
24
|
3
|
3
|
1
|
0
|
|
2
|
20
|
1
|
1
|
1
|
0
|
|
1
|
17
|
0
|
0
|
0
|
0
|
やはりメンバの1つ1つに対しても、「配列にしたときにバイト境界をまたがないように、最小限のパディングが入れられる」と言えそうです。16バイト境界だからといって、16の倍数に合わせられるということではありません。配列にしたときに、バイト境界をまたがないようなパディングが入れられるわけです。
これを踏まえたうえで、sample4_tをそのままの形で移植性を考えた構造にすると次のようになるでしょう。
struct sample4_t
{
char cvalue1;
char padding1[7];
double dvalue;
char cvalue2;
char padding2[3];
long lvalue;
char cvalue3;
char padding3[1];
short svalue;
char padding4[4];
};
|
このようにしておくと、バイト境界が1,2,4,8,16のどれであっても、構造体のサイズと、メンバのオフセット位置は変わりません。構造体を設計するときは、このようにアライメントを考えた構造にするようにしましょう。
|