検討:
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;
一方、パッキング__attribute__((__packed__))
は、コンパイラがパディングを実行できないようにします。これは明示的に要求する必要があります。GCCでは、次のようになります。
struct __attribute__((__packed__)) mystruct_A {
char a;
int b;
char c;
};
6
32ビットアーキテクチャでサイズの構造を生成します。
ただし、注意してください。アラインされていないメモリアクセスは、それを許可するアーキテクチャ(x86やamd64など)では低速であり、SPARCなどの厳密なアラインメントアーキテクチャでは明示的に禁止されています。
(上記の回答は理由を明確に説明していますが、パディングのサイズについては完全には明確ではないようです。そこで、The Lost Art of Structure Packingから学んだことに従って回答を追加します。これは、に限定されないように進化しましたC
が、Go
、にも適用可能Rust
。)
ルール:
int
は、4で割り切れるアドレス、およびlong
8で割り切れるアドレスから開始する必要がありますshort
。char
は特別で、char[]
任意のメモリアドレスにすることができるため、前にパディングする必要はありません。struct
、個々のメンバーの位置合わせの必要性を除いて、構造体全体のサイズは、最後にパディングすることにより、最大の個々のメンバーのサイズで割り切れるサイズに位置合わせされます。long
8で割り切れ、int
次に4で割り切れ、short
次に2で割り切れる場合。メンバーの順序:
stu_c
とstu_d
の例では、メンバーは同じですが、順序が異なり、2つの構造体のサイズが異なります。ルール:
(n * 16)
バイトから始まります。(以下の例でわかるように、構造体の印刷されたすべての16進アドレスはで終わり0
ます。)long double
)です。char
、そのアドレスは任意のアドレスから開始できます。空のスペース:
test_struct_address()
以下では、変数x
は隣接する構造体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です。
私はこの質問が古く、ここでのほとんどの回答がパディングを非常によく説明していることを知っていますが、それを自分で理解しようとしているときに、起こっていることの「視覚的」イメージが助けになると思いました。
プロセッサは、一定のサイズ(ワード)の「チャンク」でメモリを読み取ります。プロセッサワードの長さが8バイトだとします。これは、メモリを8バイトのビルディングブロックの大きな行と見なします。メモリから情報を取得する必要があるたびに、それらのブロックの1つに到達して取得します。
上の画像のように、Char(1バイト長)がどこにあるかは関係ありません。これは、Char(1バイト長)がこれらのブロックの1つに含まれ、CPUが1ワードのみを処理する必要があるためです。
4バイトのintや8バイトのdoubleのように、1バイトより大きいデータを処理する場合、それらをメモリ内で整列させる方法によって、CPUで処理する必要のあるワード数が異なります。4バイトのチャンクが常にブロック内に収まるように配置されている場合(メモリアドレスは4の倍数)、1ワードのみを処理する必要があります。そうしないと、4バイトのチャンクの一部が1つのブロックにあり、一部が別のブロックにある可能性があり、プロセッサがこのデータを読み取るために2ワードを処理する必要があります。
同じことが8バイトのdoubleにも当てはまりますが、常にブロック内にあることを保証するために、8の倍数のメモリアドレスにある必要があります。
これは8バイトのワードプロセッサを考慮していますが、この概念は他のサイズのワードにも適用されます。
パディングは、これらのデータ間のギャップを埋めて、それらがそれらのブロックと整列していることを確認することで機能します。これにより、メモリの読み取り中のパフォーマンスが向上します。
ただし、他の回答で述べられているように、パフォーマンス自体よりもスペースが重要な場合があります。RAMがあまりないコンピューターで大量のデータを処理している可能性があります(スワップスペースを使用できますが、処理速度が大幅に低下します)。最小のパディングが行われるまでプログラム内の変数を配置できますが(他のいくつかの回答で大いに例証されているように)、それだけでは不十分な場合は、パディングを明示的に無効にすることができます。
構造体パッキングは、構造体のパディング、位置合わせが最も重要な場合に使用されるパディング、スペースが最も重要な場合に使用されるパディングを抑制します。
一部のコンパイラは#pragma
、パディングを抑制したり、nバイト数にパックしたりするために提供しています。これを行うためのキーワードを提供するものもあります。通常、構造体のパディングを変更するために使用されるプラグマは、次の形式になります(コンパイラによって異なります)。
#pragma pack(n)
たとえば、ARMは__packed
構造体のパディングを抑制するキーワードを提供します。詳細については、コンパイラのマニュアルを参照してください。
したがって、パック構造はパディングのない構造です。
一般的にパックされた構造が使用されます
スペースを節約する
何らかのプロトコルを使用してネットワーク経由で送信するようにデータ構造をフォーマットする(エンディアンに対処する必要があるため、これはもちろん良い習慣ではありません)
パディングとパッキングは、同じことの2つの側面にすぎません。
ではmystruct_A
、デフォルトの配置を4とすると、各メンバーは4バイトの倍数で配置されます。のサイズchar
は1であるためa
、およびのパディングc
は4〜1 = 3バイトですが、int b
すでに4バイトのパディングは必要ありません。の場合も同じように機能しmystruct_B
ます。
パディングのルール:
ルール2の理由:次の構造体を検討してください。
この構造体の配列(2つの構造体)を作成する場合、最後にパディングは必要ありません。
したがって、構造体のサイズ=8バイト
以下のように別の構造体を作成するとします。
この構造体の配列を作成する場合、最後に必要なパディングのバイト数の2つの可能性があります。
A.最後に3バイトを追加し、Longではなくintに揃える場合:
B.最後に7バイトを追加し、Longに揃える場合:
2番目の配列の開始アドレスは8の倍数(つまり24)です。構造体のサイズ=24バイト
したがって、構造体の次の配列の開始アドレスを最大のメンバーの倍数に揃えることによって(つまり、この構造体の配列を作成する場合、2番目の配列の最初のアドレスは複数のアドレスで開始する必要があります構造体の最大メンバーの24(3 * 8))で、最後に必要なパディングバイト数を計算できます。
変数は、その配置(通常はサイズ)で割り切れる任意のアドレスに格納されます。したがって、パディング/パッキングは構造体専用ではありません。実際、すべてのデータには独自の配置要件があります。
int main(void) {
// We assume the `c` is stored as first byte of machine word
// as a convenience! If the `c` was stored as a last byte of previous
// word, there is no need to pad bytes before variable `i`
// because `i` is automatically aligned in a new word.
char c; // starts from any addresses divisible by 1(any addresses).
char pad[3]; // not-used memory for `i` to start from its address.
int32_t i; // starts from any addresses divisible by 4.
これは構造体に似ていますが、いくつかの違いがあります。まず、2種類のパディングがあると言えます。a)各メンバーをそのアドレスから適切に開始するために、メンバー間にいくつかのバイトが挿入されます。b)アドレスから次の構造体インスタンスを適切に開始するために、各構造体にいくつかのバイトが追加されます。
// Example for rule 1 below.
struct st {
char c; // starts from any addresses divisible by 4, not 1.
char pad[3]; // not-used memory for `i` to start from its address.
int32_t i; // starts from any addresses divisible by 4.
};
// Example for rule 2 below.
struct st {
int32_t i; // starts from any addresses divisible by 4.
char c; // starts from any addresses.
char pad[3]; // not-used memory for next `st`(or anything that has same
// alignment requirement) to start from its own address.
};
4
構造体の最初のメンバーは常に、最大のメンバーの配置要件(ここでは、の配置)によって決定される構造体自体の配置要件で割り切れるアドレスから始まりますint32_t
。これは通常の変数とは異なります。通常の変数は、その配置で割り切れる任意のアドレスを開始できますが、構造体の最初のメンバーには当てはまりません。ご存知のように、構造体のアドレスは最初のメンバーのアドレスと同じです。struct st arr[2];
。arr[1]
(の最初のメンバー)をarr[1]
4で割り切れるアドレスから開始するには、各構造体の最後に3バイトを追加する必要があります。これは私がTheLostArt ofStructurePackingから学んだことです。
注:データ型の配置要件が何であるかは、_Alignof
演算子を使用して調べることができます。offsetof
また、マクロを介して構造体内のメンバーのオフセットを取得できます。
それについては仕方がありません!主題を把握したい人は、次のことをしなければなりません、
- エリックS.レイモンドによって書かれた構造パッキングの失われた芸術を熟読する
- エリックのコード例を一瞥する
- 最後になりましたが、構造体が最大の型の配置要件に合わせて配置されるというパディングに関する次のルールを忘れないでください 。
これらの構造はパッドまたはパックされていますか?
それらは埋められています。
最初に頭に浮かぶ唯一の可能性は、それらを詰めることができる場所でchar
ありint
、同じサイズである場合です。そのため、構造の最小サイズでは、char/int/char
構造のパディングはできませんint/char
。
ただし、これには両方sizeof(int)
と4sizeof(char)
が必要です(12と8のサイズを取得するには)。常に1つの標準によって保証されているため、理論全体が崩壊します。sizeof(char)
同じ幅char
のint
場合、サイズは4と4ではなく、1と1になります。したがって、12のサイズを取得するには、最後のフィールドの後にパディングが必要になります。
パディングまたはパッキングはいつ行われますか?
コンパイラの実装が望むときはいつでも。コンパイラーは、フィールド間にパディングを自由に挿入でき、最後のフィールドの後に(ただし、最初のフィールドの前には挿入できません)。
一部のタイプは特定の境界に配置されているとパフォーマンスが向上するため、これは通常、パフォーマンスのために行われます。アラインされていないデータにアクセスしようとすると、機能を拒否する(つまり、クラッシュする)アーキテクチャもあります(はい、私はあなたを見ています、 ARM)。
一般に、などの実装固有の機能を使用して、パッキング/パディング(同じスペクトルの実際には反対側)を制御できます#pragma pack
。特定の実装でそれを実行できない場合でも、コンパイル時にコードをチェックして、要件を満たしていることを確認できます(実装固有のものではなく、標準のC機能を使用)。
例えば:
// C11 or better ...
#include <assert.h>
struct strA { char a; int b; char c; } x;
struct strB { int b; char a; } y;
static_assert(sizeof(struct strA) == sizeof(char)*2 + sizeof(int), "No padding allowed");
static_assert(sizeof(struct strB) == sizeof(char) + sizeof(int), "No padding allowed");
これらの構造にパディングがある場合、このようなものはコンパイルを拒否します。
構造体のパッキングは、コンパイラーに構造体をパックするように明示的に指示した場合にのみ実行されます。パディングはあなたが見ているものです。32ビットシステムは、各フィールドをワードアラインメントにパディングしています。コンパイラーに構造体をパックするように指示した場合、それらはそれぞれ6バイトと5バイトになります。ただし、そうしないでください。それは移植性がなく、コンパイラーがはるかに遅い(そして時にはバグのある)コードを生成するようにします。
データ構造の整列は、データがコンピュータのメモリに配置され、アクセスされる方法です。これは、データの配置とデータ構造のパディングという2つの別個の関連する問題で構成されています。最近のコンピュータがメモリアドレスからの読み取りまたはメモリアドレスへの書き込みを行う場合、これはワードサイズのチャンク(たとえば、32ビットシステムでは4バイトのチャンク)以上で行われます。データアライメントとは、ワードサイズの倍数に等しいメモリアドレスにデータを配置することを意味します。これにより、CPUがメモリを処理する方法により、システムのパフォーマンスが向上します。データを整列させるには、最後のデータ構造の終わりと次のデータ構造の始まりの間に意味のないバイトを挿入する必要がある場合があります。これはデータ構造のパディングです。