検討してください:
struct mystruct_A
{
char a;
int b;
char c;
} x;
struct mystruct_B
{
int b;
char a;
} y;
構造体のサイズはそれぞれ12と8です。
これらの構造はパッド入りですか、それともパック入りですか。
パディングまたはパッキングはいつ行われますか。
パディングは、 構造体のメンバを「自然な」アドレス境界に揃えます - たとえば、int
メンバはオフセットを持ち、それはmod(4) == 0
です。 32ビットプラットフォーム上。パディングはデフォルトでオンになっています。それはあなたの最初の構造体に以下の "ギャップ"を挿入します。
struct mystruct_A {
char a;
char gap_0[3]; /* inserted by compiler: for alignment of b */
int b;
char c;
char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;
一方、パッキングは、コンパイラーがパディングを実行できないようにします。これは明示的に要求する必要があります。GCCでは__attribute__((__packed__))
なので、以下のようになります。
struct __attribute__((__packed__)) mystruct_A {
char a;
int b;
char c;
};
32ビットアーキテクチャでサイズ6
の構造体を生成します。
ただし、注意してください - 整列されていないメモリアクセスはそれを可能にするアーキテクチャ(x86やAMD64など)では遅く、SPARCのような厳密な整列アーキテクチャでは明示的に禁止されています。
(上記の答えは理由を非常に明確に説明しましたが、パディングのサイズについては完全に明確ではないようですので、私は学んだことに従って答えを追加します構造パッキングの失われた芸術、それはC
に限らず、Go
、Rust
にも適用可能に進化しました。)
ルール:
int
は4で割り切れるアドレスから、long
は8で、short
は2で始まる必要があります。char
およびchar[]
は特別であり、任意のメモリアドレスを使用できます。そのため、それらの前にパディングする必要はありません。struct
の場合、個々のメンバーごとの位置合わせの必要性を除き、構造体全体のサイズは、最後のパディングにより、個々の最大メンバーのサイズで割り切れるサイズに位置合わせされます。long
の場合、8で割り切れる、int
、4で、short
で、次に2で割り切れます。メンバーの順序:
stu_c
とstu_d
のメンバーは同じですが、順序が異なり、2つの構造体のサイズが異なります。ルール:
(n * 16)
バイトから始まります。 (以下の例では、構造体の印刷された16進アドレスはすべて0
で終わっています)long double
)です。空のスペース:
test_struct_address()
では、変数x
は隣接するstruct g
とh
の間に存在します。x
が宣言されているかどうかに関係なく、h
のアドレスは変更されず、x
はg
が無駄にした空のスペースを再利用しました。y
の同様のケース。(64ビットシステムの場合)
memory_align.c:
/**
* Memory align & padding - for struct.
* compile: gcc memory_align.c
* execute: ./a.out
*/
#include <stdio.h>
// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
int i;
char c;
};
// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
long l;
char c;
};
// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
int i;
long l;
char c;
};
// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
long l;
int i;
char c;
};
// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
double d;
int i;
char c;
};
// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
int i;
double d;
char c;
};
// size is 4,
struct stu_g {
int i;
};
// size is 8,
struct stu_h {
long l;
};
// test - padding within a single struct,
int test_struct_padding() {
printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));
printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
return 0;
}
// test - address of struct,
int test_struct_address() {
printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));
struct stu_g g;
struct stu_h h;
struct stu_f f1;
struct stu_f f2;
int x = 1;
long y = 1;
printf("address of %s: %p\n", "g", &g);
printf("address of %s: %p\n", "h", &h);
printf("address of %s: %p\n", "f1", &f1);
printf("address of %s: %p\n", "f2", &f2);
printf("address of %s: %p\n", "x", &x);
printf("address of %s: %p\n", "y", &y);
// g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));
// h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));
// f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));
// x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));
// y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));
return 0;
}
int main(int argc, char * argv[]) {
test_struct_padding();
// test_struct_address();
return 0;
}
実行結果-test_struct_padding()
:
stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8
実行結果-test_struct_address()
:
stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0 // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0 // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8 // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8
したがって、各変数のアドレス開始はg:d0 x:dc h:e0 y:e8です
私はこの質問が古く、ここでの答えの大部分はパディングを非常によく説明していることを知っています、しかし私自身それを理解しようとしている間私は何が起こっているかの "視覚的な"イメージを持つのを助けました。
プロセッサは、一定サイズの「塊」でメモリを読み取ります(Word)。プロセッサWordの長さが8バイトだとします。それはメモリを8バイトのビルディングブロックの大きな行として見るでしょう。メモリから情報を取得する必要があるたびに、それらのブロックの1つに到達して取得されます。
上の画像にあるように、Char(1バイト長)がどこにあるかは問題ではありません。なぜなら、それはそれらのブロックのうちの1つのブロックの中にあるからです。
4バイトのintや8バイトのdoubleなど、1バイトを超えるデータを扱う場合、それらがメモリ内で整列される方法によって、CPUで処理する必要があるワード数が異なります。 4バイトのチャンクが常にブロックの内側に収まるように(メモリアドレスが4の倍数であるように)整列されている場合、1つのWordだけが処理される必要があります。そうでなければ、4バイトのチャンクの一部が別のブロックにあり、プロセッサがこのデータを読み取るために2ワードを処理する必要があります。
同じことが8バイトのdoubleにも当てはまります。ただし、常にブロック内にあることを保証するために、8の倍数のメモリアドレスになければなりません。
これは8バイトのワードプロセッサを考えています、しかし概念は他のサイズのワードに適用されます。
パディングはそれらのデータ間のギャップを埋めることによってそれらがそれらのブロックと整列されることを確実にすることによって働き、それによってメモリを読む間の性能を改善する。
ただし、他の回答で述べられているように、スペースはパフォーマンスよりも重要な場合があります。 RAMがそれほど多くないコンピュータで大量のデータを処理している可能性があります(スワップ領域を使用することはできますが、かなり遅くなります)。他の答えで大いに例証されているように、パディングが最小になるまでプログラム内で変数を調整できますが、それでも十分でない場合は、パディングを明示的に無効にすることができます。これがpackingです。
構造パッキングは、構造のパディング、配置が最も重要な場合に使用されるパディング、スペースが最も重要な場合に使用されるパディングを抑制します。
一部のコンパイラは、パディングを抑制するため、またはnバイトにパックするために#pragma
を提供しています。そのためのキーワードを提供するものもあります。一般的に構造体パディングを修正するために使用されるプラグマは以下のフォーマットになるでしょう(コンパイラによります):
#pragma pack(n)
たとえば、ARMは、構造体の埋め込みを抑制するための__packed
キーワードを提供します。これについての詳細を学ぶためにあなたのコンパイラマニュアルを見てください。
つまり、パック構造はパディングなしの構造です。
一般的な充填構造が使用されます
スペースを節約する
いくつかのプロトコルを使用してネットワークを介して送信するためにデータ構造をフォーマットすること(あなたがする必要があるのでこれはもちろん良い習慣ではありません)
エンディアンに対処する)
パディングとパッキングは、同じことの2つの側面にすぎません。
mystruct_A
では、デフォルトのアライメントを4と仮定して、各メンバーは4バイトの倍数でアライメントされています。 char
のサイズは1なので、a
およびc
のパディングは4 - 1 = 3バイトですが、int b
のパディングはすでに4バイトである必要はありません。 mystruct_B
でも同じように機能します。
構造体のパッキングは、コンパイラが構造体をパッキングするように明示的に指示した場合にのみ行われます。パディングはあなたが見ているものです。 32ビットシステムでは、各フィールドがWordの配置に合わせて埋め込まれています。コンパイラに構造体をパックするように指示した場合、それらはそれぞれ6バイトと5バイトになります。それをしないでください。移植性がなく、コンパイラがはるかに遅い(そして場合によってはバグの多い)コードを生成するようになります。
それについての考えはありません!主題を把握したい人は以下のことをしなければなりません、
- 熟読 構造パッキングの失われた芸術 Eric S. Raymond著
- 概要 Ericのコード例
- 最後に重要なことを言い忘れましたが、構造体は最大の型の配置要件に合わせて配置されるというパディングに関する次の規則を忘れないでください。