C Language
構造体のパディングとパッキング
サーチ…
前書き
デフォルトでは、Cコンパイラは構造体をレイアウトして、各メンバーに、アライメントのないアクセス、DEC Alphaや一部のARM CPUなどのRISCマシンの問題に対するペナルティを課すことなく、高速アクセスが可能です。
CPUアーキテクチャーとコンパイラーによって、構造体は、その構成メンバーのサイズの合計よりも多くのメモリー領域を占有する可能性があります。コンパイラは、メンバー間または構造の最後にパディングを追加できますが、最初は追加しません。
パッキングはデフォルトのパディングをオーバーライドします。
備考
エリックレイモンドは、 C構造パッキングのロストアートに関する記事を読んでいます。
パッキング構造
デフォルトでは、構造体はC言語で埋められます。この動作を避けたい場合は、明示的に要求する必要があります。 GCCでは__attribute__((__packed__))
です。 64ビットマシンでこの例を考えてみましょう。
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
構造体は自動的に8-byte
境界になるようにパディングされ、次のようになります。
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
char pad[7]; /* 7 bytes added by compiler */
long x; /* 8 bytes */
};
sizeof(struct foo)
は17
代わりに24
sizeof(struct foo)
ます。これは、64ビットコンパイラが各ステップで8バイトのワードでメモリから読み書きするために発生し、 char c;
を書き込もうとすると明らかchar c;
メモリ内の1バイトは完全な8バイト(すなわちワード)がフェッチされ、その最初のバイトのみを消費し、その7つの連続したバイトは空のままであり、構造体パディングのための読出しおよび書込み操作に対してアクセスできない。
構造パッキング
しかし、 packed
属性を追加すると、コンパイラはパディングを追加しません。
struct __attribute__((__packed__)) foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
sizeof(struct foo)
は17
を返します。
一般に、パック構造が使用されます:
- スペースを節約する。
- ネットワークの各ノードの各アーキテクチャのアラインメントに依存することなく、ネットワークを介して送信するようにデータ構造をフォーマットすること。
ARM Cortex-M0などの一部のプロセッサでは、アラインされていないメモリアクセスが許可されていないことを考慮しなければなりません。そのような場合、構造体パッキングは未定義の動作につながり、CPUをクラッシュさせる可能性があります。
構造体のパディング
このstruct
が定義され、32ビットコンパイラでコンパイルされたとします。
struct test_32 {
int a; // 4 byte
short b; // 2 byte
int c; // 4 byte
} str_32;
このstruct
はメモリの10バイトしか占有しないと予想されるかもしれませんが、 sizeof(str_32)
を出力すると12バイトが使用されます。
これは、高速アクセスのためにコンパイラが変数を整列させるために起こりました。一般的なパターンは、基本型がNバイト(Nは1,2,4,8,16などの2の累乗で、それ以上はそれほど大きくない場合)を占める場合、変数はNバイトの境界に整列する必要がありますNバイトの倍数)。
sizeof(int) == 4
およびsizeof(short) == 2
で示された構造の場合、一般的なレイアウトは次のようになります。
-
int a;
オフセット0に格納されます。サイズ4。 -
short b;
オフセット4に格納される。サイズ2。 - オフセット6の名前のないパディング。サイズ2。
-
int c;
オフセット8で格納される。サイズ4。
したがって、 struct test_32
は12バイトのメモリを占有します。この例では、後続のパディングはありません。
コンパイラは、 struct test_32
のメンバーが高速アクセスのために適切に配置されるように、4バイト境界から始まるstruct test_32
変数が確実に格納されるようにします。 malloc()
、 calloc()
、 realloc()
などのメモリ割り当て関数は、返されるポインタがどのデータ型でも十分に整列され、動的に割り当てられた構造体も正しく整列されるようにするために必要です。
32ビットモードでコンパイルすると、コンパイラがdouble
場所に配置された64ビットIntel x86_64プロセッサ(Intel Core i7 - MacOS SierraまたはMac OS Xを実行しているMacなど)のような奇妙な状況に終わることがあります。 4バイトの境界。同じハードウェア上では、64ビット・モードでコンパイルすると、コンパイラーは8バイトの境界にdouble
配置されます。