web-dev-qa-db-ja.com

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

検討してください:

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

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

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

これらの構造はパッド入りですか、それともパック入りですか。

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

175
Manu

パディングは、 構造体のメンバを「自然な」アドレス境界に揃えます - たとえば、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のような厳密な整列アーキテクチャでは明示的に禁止されています。

232

上記の答えは理由を非常に明確に説明しましたが、パディングのサイズについては完全に明確ではないようですので、私は学んだことに従って答えを追加します構造パッキングの失われた芸術、それはCに限らず、GoRustにも適用可能に進化しました。


メモリアライン(構造体用)

ルール:

  • 各個々のメンバーの前に、サイズで割り切れるアドレスから開始するようにパディングがあります。
    たとえば、64ビットシステムでは、intは4で割り切れるアドレスから、longは8で、shortは2で始まる必要があります。
  • charおよびchar[]は特別であり、任意のメモリアドレスを使用できます。そのため、それらの前にパディングする必要はありません。
  • structの場合、個々のメンバーごとの位置合わせの必要性を除き、構造体全体のサイズは、最後のパディングにより、個々の最大メンバーのサイズで割り切れるサイズに位置合わせされます。
    e.g構造体の最大メンバーがlongの場合、8で割り切れる、int、4で、shortで、次に2で割り切れます。

メンバーの順序:

  • メンバーの順序は、構造体の実際のサイズに影響を与える可能性があるため、そのことに留意してください。たとえば、以下の例のstu_cstu_dのメンバーは同じですが、順序が異なり、2つの構造体のサイズが異なります。

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

ルール:

  • 64ビットシステム
    構造アドレスは(n * 16)バイトから始まります。 (以下の例では、構造体の印刷された16進アドレスはすべて0で終わっています
    Reason:可能な最大の個々の構造体メンバーは16バイト(long double)です。

空のスペース

  • 2つの構造体の間の空のスペースは、適合する非構造体変数によって使用される可能性があります。
    e.g。下のtest_struct_address()では、変数xは隣接するstruct ghの間に存在します。
    xが宣言されているかどうかに関係なく、hのアドレスは変更されず、xgが無駄にした空のスペースを再利用しました。
    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です

enter image description here

40
Eric Wang

私はこの質問が古く、ここでの答えの大部分はパディングを非常によく説明していることを知っています、しかし私自身それを理解しようとしている間私は何が起こっているかの "視覚的な"イメージを持つのを助けました。

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

Variables Alignment

上の画像にあるように、Char(1バイト長)がどこにあるかは問題ではありません。なぜなら、それはそれらのブロックのうちの1つのブロックの中にあるからです。

4バイトのintや8バイトのdoubleなど、1バイトを超えるデータを扱う場合、それらがメモリ内で整列される方法によって、CPUで処理する必要があるワード数が異なります。 4バイトのチャンクが常にブロックの内側に収まるように(メモリアドレスが4の倍数であるように)整列されている場合、1つのWordだけが処理される必要があります。そうでなければ、4バイトのチャンクの一部が別のブロックにあり、プロセッサがこのデータを読み取るために2ワードを処理する必要があります。

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

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

パディングはそれらのデータ間のギャップを埋めることによってそれらがそれらのブロックと整列されることを確実にすることによって働き、それによってメモリを読む間の性能を改善する。

ただし、他の回答で述べられているように、スペースはパフォーマンスよりも重要な場合があります。 RAMがそれほど多くないコンピュータで大量のデータを処理している可能性があります(スワップ領域を使用することはできますが、かなり遅くなります)。他の答えで大いに例証されているように、パディングが最小になるまでプログラム内で変数を調整できますが、それでも十分でない場合は、パディングを明示的に無効にすることができます。これがpackingです。

36
IanC

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

一部のコンパイラは、パディングを抑制するため、またはnバイトにパックするために#pragmaを提供しています。そのためのキーワードを提供するものもあります。一般的に構造体パディングを修正するために使用されるプラグマは以下のフォーマットになるでしょう(コンパイラによります):

#pragma pack(n)

たとえば、ARMは、構造体の埋め込みを抑制するための__packedキーワードを提供します。これについての詳細を学ぶためにあなたのコンパイラマニュアルを見てください。

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

一般的な充填構造が使用されます

  • スペースを節約する

  • いくつかのプロトコルを使用してネットワークを介して送信するためにデータ構造をフォーマットすること(あなたがする必要があるのでこれはもちろん良い習慣ではありません)
    エンディアンに対処する)

20
user2083050

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

  • 梱包または配置は、各メンバーの四捨五入サイズです。
  • パディングは、位置合わせに合わせて追加されるスペースです。

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

5
casablanca

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

1
nmichaels

それについての考えはありません!主題を把握したい人は以下のことをしなければなりません、

0
snr