構造のパディングとパッキング


209

考慮してください:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

構造のサイズはそれぞれ12と8です。

これらの構造物はパディングまたはパックされていますか?

パディングまたはパッキングはいつ行われますか?



24
The Lost Art of C Structure Packing-catb.org/esr/structure-packing
Paolo

padding物事を大きくします。packing物事を小さくします。全く違います。
smwikipedia 2017年

回答:


264

パディング は、構造体のメンバーを「自然な」アドレス境界に揃えます。たとえば、intメンバーにはmod(4) == 032ビットプラットフォームのオフセットがあります。パディングはデフォルトでオンになっています。次の「ギャップ」を最初の構造に挿入します。

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;
};

632ビットアーキテクチャでサイズの構造を生成します。

ただし、非整列メモリーアクセスは、それを許可するアーキテクチャー(x86やamd64など)では低速であり、SPARCなどの厳密な整列アーキテクチャーでは明示的に禁止されています。


2
私は疑問に思います:スパークでの非整列メモリの禁止は、通常のバイト配列を処理できないことを意味しますか?バイト配列を構造体にキャストする必要があり、配列が構造体フィールドに適合していることを確認する必要がある場合、データの送信(つまりネットワーキング)で主に使用されている構造体パッキング。スパークがそれを行うことができない場合、それらはどのように機能するのですか?
Hi-Angel

14
そのため、IP、UDP、TCPのヘッダーレイアウトを見ると、すべての整数フィールドが揃っていることがわかります。
ニコライフェティソフ2014年

17
「Lost Art of C Structure Packing」では、パディングとパッキングの最適化について説明しています-catb.org/esr/structure-packing
Rob11311

3
最初のメンバーが最初に来る必要がありますか?arragementは完全に実装次第であり、(バージョン間でも)信頼できないと思いました。
allyourcode 2015

4
+ allyourcode標準では、メンバーの順序が保持され、最初のメンバーがオフセット0から始まることが保証されています。
martinkunev 2017

64

上記の答えは非常に明確な理由を説明したが、完全ので、私は私から学んだことに基づいて答えを追加します、パディングのサイズについて明確ではないと思われる構造体パッキングのロストアート、それがない限界まで進化してきたCが、また、適用しますGoRust


メモリ整列(構造体用)

ルール:

  • 個々のメンバーの前に、そのサイズで割り切れるアドレスから開始するようにパディングがあります。
    たとえば、64ビットシステムでintは、4で割り切れるアドレスlong、8で割り切れるアドレス、2 で始まる必要がありますshort
  • charそしてchar[]、彼らは彼らの前にパディングを必要としないので、任意のメモリアドレス可能性があり、特別です。
  • 以下のためにstruct、各個々のメンバーのアライメント必要以外、全体構造体自体のサイズは終わりにパディングすることによって、最大の個々のメンバーのサイズによってサイズ割り切れるに整列されます。
    例えば、構造体の最大のメンバーである場合long、8で割り切れるそしてint、その後、4でshort、その後2で。

メンバーの順番:

  • メンバーの順序は構造体の実際のサイズに影響する可能性があるので、それを覚えておいてください。例えばstu_cおよびstu_d以下の例から同じメンバーを持っていますが、異なる順序で、及び2つの構造体のために異なるサイズになります。

メモリ内のアドレス(構造体用)

ルール:

  • 64ビットのシステム
    Structアドレスは(n * 16)バイトから始まります。(以下の例では、構造体の印刷された16進アドレスはすべてで終わり0ます。
    理由:可能な最大の個々の構造体メンバーは16バイト(long double)です。
  • (更新)構造体にcharasメンバーのみが含まれる場合、そのアドレスは任意のアドレスから開始できます。

空のスペース

  • 2つの構造体の間の空のスペースは、構造体以外の変数によって使用される可能性があります。
    たとえば、test_struct_address()以下でxは、隣接する構造体gとの間に変数が存在しますh。が宣言されて
    いるかどうかに関係なく、のアドレスは変更されず、浪費された空のスペースが再利用されます。 の同様のケース。xhxg
    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です。

ここに画像の説明を入力してください


4
「ルール」は実際にそれを非常に明確にしました。ありがとう。
ペルベスアラム2017

2
@PervezAlam本は<The Lost Art of C Structure Packing>、この答えよりも少し長いと思っていても、ルールをかなりよく説明しています。この本は無料でオンラインで入手できます:catb.org/esr/structure-packing
Eric Wang

私はそれを試してみます、ところでそれは構造パッキングに限定されていますか?本の説明が好きだったので骨董品。
ペルベスアラム2017

1
@PervezAlamこれは非常に短い本であり、主にcプログラムのメモリフットプリントを削減するテクノロジーに焦点を当てており、読むのに最長で数日しかかかりません。
エリックワン

1
@ValidusOculusはい、16バイト境界で整列されます。
Eric Wang

44

私はこの質問が古いことを知っており、ここでのほとんどの回答はパディングを非常にうまく説明していますが、私自身がそれを理解しようとしている間に、起こっていることの「視覚的な」イメージがあると思いました。

プロセッサは、一定のサイズ(ワード)の「チャンク」でメモリを読み取ります。プロセッサワードの長さが8バイトであるとします。これは、メモリを8バイトのビルディングブロックの大きな行と見なします。メモリから情報を取得する必要があるたびに、これらのブロックのいずれかに到達して取得します。

変数の整列

上の画像のように、Char(1バイト長)がどこにあるかは関係ありません。これは、これらのブロックの1つ内にあり、CPUが1ワードのみを処理する必要があるためです。

4バイトの整数や8バイトの倍数など、1バイトより大きいデータを処理する場合、メモリ内でのデータの整列方法によって、CPUで処理する必要のあるワード数が異なります。4バイトのチャンクが常にブロックの内側に収まるように配置されている場合(メモリアドレスは4の倍数)、1ワードのみを処理する必要があります。それ以外の場合、4バイトのチャンクは、ブロックの一部と別のブロックの両方を持ち、プロセッサがこのデータを読み取るために2ワードを処理する必要があります。

同じことが8バイトdoubleにも当てはまりますが、常にブロック内にあることを保証するために、8の倍数のメモリアドレスになければなりません。

これは8バイトのワードプロセッサを考慮していますが、この概念は他のサイズのワードにも適用されます。

パディングは、それらのデータ間のギャップを埋めてそれらがそれらのブロックと整列していることを確認することで機能し、メモリの読み取り中のパフォーマンスを向上させます。

ただし、他の回答に記載されているように、パフォーマンスよりもスペースの方が重要な場合があります。RAMが少ないコンピュータで大量のデータを処理している可能性があります(スワップ領域を使用することはできますが、処理速度は非常に遅くなります)。最小のパディングが行われるまで(他の回答で大きく例示されているように)、プログラム内の変数を配置できますが、それで十分でない場合は、パッキングとは明示的にパディングを無効にできます。


3
これは構造のパッキングを説明していませんが、CPUワードの配置を非常にうまく示しています。
David Foerster

それをペンキで描いたの?:-)
Ciro Santilli郝海东冠状病六四事件法轮功

1
@ CiroSantilli709大掴捕六四事件法轮功、それはひどいものでしたが、私はペイントでそれをするのに時間を節約したと思います
IanC

1
オープンソース(Y)以来さらに良い
Ciro Santilli郝海东冠状病六四事件法轮功

21

構造パッキングは、構造パディングを抑制します。配置が最も重要な場合に使用されるパディング、スペースが最も重要な場合に使用されるパッキングです。

一部のコンパイラでは#pragma、パディングを抑制したり、nバイトにパックしたりできます。これを行うためのキーワードを提供するものもあります。一般に、構造パディングの変更に使用されるプラグマは、次の形式になります(コンパイラによって異なります)。

#pragma pack(n)

たとえば、ARMには__packed構造パディングを抑制するキーワードが用意されています。これについては、コンパイラのマニュアルを参照してください。

つまり、パック構造はパディングのない構造です。

一般的にパックされた構造が使用されます

  • スペースを節約する

  • 何らかのプロトコルを使用してネットワーク経由で送信するデータ構造をフォーマットする(
    エンディアンを処理する必要があるため、これはもちろん良い方法ではありません)


5

パディングとパッキングは、同じことの2つの側面にすぎません。

  • パッキングまたは配置は、各メンバーが四捨五入されるサイズです
  • パディングは、位置合わせと一致するように追加される追加のスペースです

ではmystruct_A、デフォルトのアラインメントを4とすると、各メンバーは4バイトの倍数にアラインされます。のサイズは1なのでcharaとのパディングcは4-1 = 3バイトですint bが、すでに4バイトのパディングは必要ありません。でも同じように機能しmystruct_Bます。


1

構造体のパッキングは、コンパイラーに構造体をパックするよう明示的に指示した場合にのみ行われます。パディングはあなたが見ているものです。32ビットシステムでは、各フィールドにワードアラインメントが埋め込まれます。コンパイラーに構造体をパックするように指示した場合、それらはそれぞれ6バイトと5バイトになります。それをしないでください。これは移植性がなく、コンパイラーがはるかに遅い(場合によってはバグの多い)コードを生成します。


1

それについてお尻はありません!主題を把握したい人は、次のことを行う必要があります、


1

パディングのルール:

  1. 構造体のすべてのメンバーは、そのサイズで割り切れるアドレスにある必要があります。要素間または構造体の最後にパディングが挿入され、このルールが確実に満たされるようにします。これは、ハードウェアによるバスアクセスをより簡単かつ効率的にするために行われます。
  2. 構造体の最後のパディングは、構造体の最大のメンバーのサイズに基づいて決定されます。

ルール2の理由:次の構造体を検討してください。

構造1

この構造体の(2つの構造体の)配列を作成する場合、最後にパディングは必要ありません。

Struct1配列

したがって、構造体のサイズ= 8バイト

次のように別の構造体を作成するとします。

構造2

この構造体の配列を作成する場合、最後に必要なパディングのバイト数には2つの可能性があります。

A.最後に3バイトを追加し、それをLongではなくintに揃えると、次のようになります。

Intに位置合わせされたStruct2配列

B.最後に7バイトを追加し、それをLongに揃えると、次のようになります。

Longに位置合わせされたStruct2配列

2番目の配列の開始アドレスは8の倍数(つまり24)です。構造体のサイズ= 24バイト

したがって、構造体の次の配列の開始アドレスを最大のメンバーの倍数に揃えることで(つまり、この構造体の配列を作成する場合、2番目の配列の最初のアドレスは、倍数のアドレスから開始する必要があります)構造体の最大のメンバーのここで、これは24(3 * 8))であり、最後に必要なパディングバイト数を計算できます。


-1

データ構造のアラインメントは、データがコンピューターのメモリに配置されアクセスされる方法です。これは、2つの別個の関連する問題で構成されています。データの配置とデータ構造のパディングです。最近のコンピューターがメモリアドレスの読み取りまたは書き込みを行う場合、これはワードサイズのチャンク(32ビットシステムでは4バイトチャンクなど)以上で行われます。データアライメントとは、ワードサイズの倍数に等しいメモリアドレスにデータを配置することを意味します。これにより、CPUによるメモリの処理方法により、システムのパフォーマンスが向上します。データを整列するには、最後のデータ構造の終わりと次のデータ構造のパディングである次のデータ構造の始まりの間に、意味のないバイトを挿入する必要がある場合があります。

  1. メモリ内のデータを整列させるために、メモリ割り当て中に他の構造体メンバーに割り当てられたメモリアドレスの間に1つ以上の空のバイト(アドレス)が挿入(または空のまま)されます。この概念は、構造パディングと呼ばれます。
  2. コンピュータプロセッサのアーキテクチャは、メモリから一度に1ワード(32ビットプロセッサでは4バイト)を読み取ることができる方法です。
  3. プロセッサのこの利点を利用するために、データは常に4バイトパッケージとして整列され、他のメンバーのアドレスの間に空のアドレスが挿入されます。
  4. Cでのこの構造パディングの概念のため、構造のサイズは常に私たちが考えるものと同じではありません。

1
回答で同じ記事に5回リンクする必要があるのはなぜですか?例へのリンクは1つだけ保持してください。また、記事にリンクしているので、その事実を開示する必要があります。
Artjom B.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.