C言語編 目次

 関数設計

 

・関数名の命名規則
・プログラミングに出る!英単語

 ポインタ

 

・データ型とポインタ

 データ型

 

・char *とconst char *は違う
・符号付きと符号なし

 演算子

 

・三項演算子とデータ型の問題

 制御構文

 

・条件式で代入する
・三項演算子を使ったswitch

 構造体

 

・構造体のサイズとアライメント
・構造体メンバのサイズを知る

 配列

 

・配列使用時の注意
・配列の要素数を知る

 メモリ管理

 

・メモリスタック
・動的メモリ確保とメモリリーク

 モジュール設計

 

・モジュール分割
・汎用モジュールとアプリ依存モジュール

 パフォーマンス
  徹底チューニング

 


・どんな処理に時間がかかるのか
・ファイル入出力の効率化
・アルゴリズムを考える1
・アルゴリズムを考える2

 プリプロセッサの便利機能


・2重インクルード防止

 


トップページへ戻る

構造体のサイズとアライメント

 構造体のサイズは、単純に構造体のメンバのサイズを合計したものにはならないときがあります。具体的な例を見てみましょう。

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のどれであっても、構造体のサイズと、メンバのオフセット位置は変わりません。構造体を設計するときは、このようにアライメントを考えた構造にするようにしましょう。