組込みシステム開発において、特にメモリ制約が厳しい環境では、構造体のサイズ最適化は重要な課題である。Windows CE(WinCE)のようなプラットフォームでも、構造体のメモリレイアウトを理解することは不可欠である。以下では、構造体のサイズ計算とアライメントルールについて実例を通じて解説する。
基本的な構造体とアライメント
構造体のサイズは、単にメンバ変数のサイズの合計ではなく、アライメント(alignment)ルールによって決まる。一般的に、構造体全体のサイズは「構造体内で最も大きい基本型のサイズ」の倍数となる。
typedef struct {
char a;
char b;
} TwoBytes;
typedef struct {
char a;
short s;
} WithShort;
typedef struct {
char a;
int i;
} WithInt;
typedef struct {
char a;
double d;
} WithDouble;
上記の構造体において、WithShort は short(2バイト)にアライメントされ、全体サイズは4バイトとなる。一方、TwoBytes は最大メンバが1バイトのため、サイズは2バイトのままとなる。
構造体の順序によるサイズ変化
メンバの宣言順序が構造体のサイズに影響を与える例を示す:
typedef struct {
char c1;
char c2;
int i;
char c3;
char c4;
} OrderA; // サイズ: 12
typedef struct {
int i;
char c1;
char c2;
char c3;
char c4;
} OrderB; // サイズ: 8
OrderA では、int の前に2バイトの char があるため、int を4バイト境界に配置するために2バイトのパディングが挿入される。その後、末尾にも再び4バイト境界に合わせるために4バイトのパディングが追加され、合計12バイトとなる。
一方、OrderB は最初に4バイト境界に配置された int の後に連続して4つの char を配置でき、末尾で8バイト境界に自然に揃うため、追加パディングが不要でサイズは8バイトとなる。
ネストされた構造体とアライメント
構造体をメンバとして含む場合、その内部構造もアライメントに影響する:
typedef struct {
char a;
TwoBytes tb; // {char, char}
} NestedTwo;
typedef struct {
char a;
WithInt wi; // {char, int}
} NestedInt;
NestedTwo のサイズは3バイト(最大メンバは1バイト)、NestedInt は5バイト(最大メンバは4バイトだが、構造体全体のアライメントは4バイトの倍数ではないため、5バイトのまま)。ただし、この構造体を配列や他の構造体に含める場合、外部でのアライメント要件によりパディングが追加される可能性がある。
ポインタとビットフィールドの影響
ポインタやビットフィールドを含む構造体も同様にアライメントルールに従う:
struct Example1 {
char* p; // 4バイト
char c; // 1バイト
long x; // 4バイト → 合計12バイト(cの後に3バイトのパディング)
};
struct Example2 {
char c; // 1バイト
char pad[7]; // 明示的パディング
char* p; // 4バイト
long x; // 4バイト → 合計16バイト
};
struct BitField {
short s; // 2バイト
char c; // 1バイト
int flip : 1; // ビットフィールド(intベース)
int nybble : 4;
int septet : 7; // 合計8バイト(sとcの後に1バイトパディング、その後intアライメント)
};
実行結果(WinCEおよびx86共通)
size of char is 1 size of short is 2 size of int is 4 size of long is 4 size of char * is 4 size of float is 4 size of double is 8 size of struct(char,char,int,char,char) is 12 size of struct(int,char,char,char,char) is 8 size of struct(int,char,char,char) is 8 size of struct(char,short) is 4 size of struct(char,TwoBytes) is 3 size of struct(char,int) is 8 size of struct(char,FourBytes) is 5 size of struct(char,double) is 16 size of struct(char,EightBytes) is 9 size of (struct Example1) = 12 size of (struct Example2) = 16 size of (struct with char*, char) = 8 size of (struct with short, char) = 4 size of (struct with bit-fields) = 8
これらの結果から、構造体のメモリ使用量を最小化するには、大きなアライメント要件を持つメンバを先に宣言し、小さなメンバを後に配置することが有効であることがわかる。また、構造体を再利用可能なコンポーネントとして設計する際には、そのアライメント特性を考慮する必要がある。