C 言語での構造体の整列とパディングの使用
この記事では、C 言語で struct
の整列とパディングを使用する方法をいくつか説明します。
C 言語での整列とパディングの基本を理解する
メモリ上のすべてのオブジェクトは、以下のような一次データ型で表現されます。char
、short
、int
、long
、pointer
などです。これらのデータ型はメモリ上で対応するサイズを持っています。現代のほとんどの 64 ビットデスクトッププロセッサでは、char
は 1 バイト、short
は 2 バイト、int
は 4 バイト、pointer
は 8 バイト、などのサイズになっています。これらのサイズは保証されたものではないことに注意してください(char
を除く)が、オブジェクトのサイズは sizeof
演算子を用いて取得できます。さて、アラインメントはコンパイラが変数をメモリに配置するために採用する方法であり、各基本データ型は対応するサイズで割り切れるアドレスに格納されることを意味しています。
通常、アラインメントはデータオブジェクトに高速かつ効率的にアクセスするために利用されます。アライメントは、連続的に宣言された異なるデータ型に対して、それらのアドレス間の間隔を強制的に含ませます。つまり、次の例に示すように、1つのポインタと char
を持つ構造体 st1
を宣言すると、合計で 16 バイトを消費することになります。しかし、単一のポインタは 8 バイト、char
は 1 バイトですから、st1
構造体は 9 バイトを占有しなければならないと考えるでしょう。しかし、すべてのメンバが最大のメンバサイズ(つまり 8 バイト)にアラインメントされているかのように動作します。st2
構造体は、7つの char
メンバの配列を持つことを除いては、同じメモリ量を占有する同様の構造体を示しています。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
typedef struct {
char *p;
char c2;
} st1;
typedef struct {
char *p;
char c2;
char padding[7];
} st2;
printf("sizeof st1 = %zu\n", sizeof(st1));
printf("sizeof st2 = %zu\n", sizeof(st2));
exit(EXIT_SUCCESS);
}
出力:
sizeof st1 = 16
sizeof st2 = 16
メンバの並び替えテクニックを使用して C 言語のオブジェクトのスペースを節約する
前の例では、構造体が異なるタイプを含み、整列境界を埋めない場合にメモリの無駄があることを示しました。しかし、構造体のメンバを並べ替えて余分なスペースを節約できる場合もあります。
次のコード例では、真ん中に最大のメンバ (char *
) を持つ foo1
構造体と、最初のメンバと同じメンバを持つ foo2
構造体を定義しています。この 2つのオブジェクトのサイズは 24 バイトと 16 バイトと異なっています。これはデータメンバの順序に関係しています。foo1
構造体では、p
は 8 で割れるアドレスに整列する必要があるため、その前の int
と short
が合計 8 バイトを占め、その後の 2つの char *
も 8 バイトを占めます。しかし、p
を最初の位置に移動させれば、それに続くメンバが 8 バイトに収まるので、アライメントルールも満たされます。このようにして、foo2
のサイズは合計 16 バイトとなり、struct
を詰めるように呼ばれることになります。gcc
コンパイラは特別な __attribute__ ((packed))
指定子を持っていて、順序のない struct
メンバであっても強制的にパックさせることができることに注意してください。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
typedef struct {
int n1;
short s1;
char *p;
char c1;
char c2;
} foo1;
typedef struct {
char *p;
int n1;
short s1;
char c1;
char c2;
} foo2;
typedef struct {
int n1;
short s1;
char *p;
char c1;
char c2;
} __attribute__((packed)) foo3;
printf("sizeof foo1 = %zu\n", sizeof(foo1));
printf("sizeof foo2 = %zu\n", sizeof(foo2));
printf("sizeof foo3 = %zu\n", sizeof(foo3));
exit(EXIT_SUCCESS);
}
出力:
sizeof foo1 = 24
sizeof foo2 = 16
sizeof foo3 = 16