it-swarm.com.de

Strukturpolsterung und Verpackung

Erwägen:

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

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

Die Größen der Strukturen betragen 12 bzw. 8.

Sind diese Strukturen gepolstert oder gepackt?

Wann wird gepolstert oder gepackt?

177
Manu

Padding richtet Strukturelemente an "natürlichen" Adressgrenzen aus - sagen wir, int Elemente hätten Offsets, die are mod(4) == 0 auf einer 32-Bit-Plattform. Das Auffüllen ist standardmäßig aktiviert. Es fügt die folgenden "Lücken" in Ihre erste Struktur ein:

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;

Packing verhindert hingegen das Auffüllen des Compilers - dies muss explizit angefordert werden - unter GCC ist es __attribute__((__packed__)), also das Folgendes:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

würde eine Struktur der Größe 6 auf einer 32-Bit-Architektur erzeugen.

Ein Hinweis: - Nicht ausgerichteter Speicherzugriff ist auf Architekturen, die dies zulassen, langsamer (wie x86 und AMD64) und ist auf Architekturen mit strikter Ausrichtung wie SPARC ausdrücklich untersagt.

238

( Die obigen Antworten haben den Grund ziemlich klar erklärt, scheinen aber nicht ganz klar über die Größe der Polsterung zu sein. Daher werde ich eine Antwort hinzufügen, die dem entspricht, was ich aus gelernt habe. Die verlorene Kunst der Strukturverpackung, die nicht nur auf C, sondern auch auf Go beschränkt ist, Rust.)


Memory Align (für Struktur)

Regeln:

  • Vor jedem einzelnen Mitglied wird ein Auffüllen vorgenommen, damit es an einer Adresse beginnt, die durch ihre Größe teilbar ist.
    Beispielsweise sollte auf einem 64-Bit-System int bei einer durch 4 teilbaren Adresse beginnen und long bei 8, short bei 2.
  • char und char[] Sind spezielle Speicheradressen. Sie müssen also nicht vor ihnen aufgefüllt werden.
  • Für struct wird, abgesehen von der Ausrichtungsanforderung für jedes einzelne Element, die Größe der gesamten Struktur selbst durch Auffüllen am Ende auf eine Größe ausgerichtet, die durch die Größe des größten einzelnen Elements teilbar ist.
    Wenn zum Beispiel das größte Mitglied von struct long ist, dann teilbar durch 8, int dann durch 4, short dann durch 2.

Reihenfolge der Mitglieder:

  • Die Reihenfolge der Mitglieder kann sich auf die tatsächliche Größe der Struktur auswirken. Beispielsweise haben stu_c und stu_d aus dem folgenden Beispiel dieselben Elemente, jedoch in unterschiedlicher Reihenfolge, und führen zu einer unterschiedlichen Größe für die beiden Strukturen.

Adresse im Speicher (für Struktur)

Regeln:

  • 64-Bit-System
    Die Strukturadresse beginnt bei (n * 16) Bytes. ( Wie Sie im folgenden Beispiel sehen können, enden alle gedruckten Hexadezimaladressen von Strukturen mit 0.)
    Grund : Das mögliche größte einzelne Strukturelement ist 16 Byte (long double).

Leerzeichen :

  • Der Leerraum zwischen zwei Strukturen könnte von Nicht-Strukturvariablen verwendet werden, die in diese passen könnten.
    B. in test_struct_address() unten, befindet sich die Variable x zwischen der benachbarten Struktur g und h.
    Unabhängig davon, ob x deklariert ist, ändert sich die Adresse von h nicht. x hat nur den leeren Platz wiederverwendet, den g verschwendet hat .
    Ähnliches gilt für y.

Beispiel

( für 64-Bit-System)

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

Ausführungsergebnis - 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

Ausführungsergebnis - 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

Somit ist der Adressanfang für jede Variable g: d0 x: dc h: e0 y: e8

enter image description here

40
Eric Wang

Ich weiß, diese Frage ist alt und die meisten Antworten hier erklären die Polsterung sehr gut, aber als ich versuchte, sie selbst zu verstehen, kam mir die Idee, ein "visuelles" Bild davon zu haben, was gerade passiert.

Der Prozessor liest den Speicher in "Stücken" einer bestimmten Größe (Word). Angenommen, das Prozessorwort ist 8 Byte lang. Der Speicher wird als eine große Reihe von 8-Byte-Bausteinen betrachtet. Jedes Mal, wenn Informationen aus dem Speicher abgerufen werden müssen, wird einer dieser Blöcke erreicht und abgerufen.

Variables Alignment

Wie im obigen Bild zu sehen ist, spielt es keine Rolle, wo sich ein Zeichen (1 Byte lang) befindet, da es sich in einem dieser Blöcke befindet und die CPU nur 1 Wort verarbeiten muss.

Wenn wir mit Daten arbeiten, die größer als ein Byte sind, wie z. B. ein 4-Byte-Int- oder ein 8-Byte-Double, ist die Ausrichtung im Speicher entscheidend dafür, wie viele Wörter von der CPU verarbeitet werden müssen. Wenn 4-Byte-Blöcke so ausgerichtet sind, dass sie immer in einen Block passen (wobei die Speicheradresse ein Vielfaches von 4 ist), muss nur ein Wort verarbeitet werden. Andernfalls könnte ein Teil von 4 Bytes einen Teil von sich selbst in einem Block und einen anderen Teil in einem anderen Block haben, so dass der Prozessor 2 Wörter verarbeiten muss, um diese Daten zu lesen.

Dasselbe gilt für ein 8-Byte-Double, außer dass es sich jetzt in einem Speicheradressen-Vielfachen von 8 befinden muss, um sicherzustellen, dass es sich immer innerhalb eines Blocks befindet.

Hierbei wird ein 8-Byte-Textverarbeitungsprogramm berücksichtigt, das Konzept gilt jedoch auch für andere Wortgrößen.

Beim Auffüllen werden die Lücken zwischen diesen Daten gefüllt, um sicherzustellen, dass sie an diesen Blöcken ausgerichtet sind, wodurch die Leistung beim Lesen des Speichers verbessert wird.

Wie in anderen Antworten angegeben, ist jedoch manchmal der Raum wichtiger als die Leistung selbst. Möglicherweise verarbeiten Sie viele Daten auf einem Computer, der nicht über viel RAM verfügt (der Auslagerungsspeicher kann verwendet werden, ist jedoch VIEL langsamer). Sie können die Variablen im Programm anordnen, bis die geringste Auffüllung erfolgt ist (wie in einigen anderen Antworten deutlich gezeigt wurde). Wenn dies jedoch nicht ausreicht, können Sie die Auffüllung explizit deaktivieren. Dies ist packing.

38
IanC

Strukturpackung unterdrückt Strukturpolsterung, Polsterung wird verwendet, wenn die Ausrichtung am wichtigsten ist, Packung wird verwendet, wenn der Platz am wichtigsten ist.

Einige Compiler bieten #pragma, um das Auffüllen zu unterdrücken oder auf n Bytes zu packen. Einige bieten Schlüsselwörter, um dies zu tun. Im Allgemeinen hat Pragma, das zum Ändern des Strukturauffüllens verwendet wird, das folgende Format (abhängig vom Compiler):

#pragma pack(n)

Zum Beispiel liefert ARM das __packed Schlüsselwort zum Unterdrücken des Strukturauffüllens. Sehen Sie sich Ihr Compiler-Handbuch an, um mehr darüber zu erfahren.

Eine gepackte Struktur ist also eine Struktur ohne Polsterung.

Im Allgemeinen werden gepackte Strukturen verwendet

  • um platz zu sparen

  • zum Formatieren einer Datenstruktur für die Übertragung über ein Netzwerk mithilfe eines Protokolls (dies ist natürlich keine gute Vorgehensweise, da dies erforderlich ist
    beschäftige dich mit Endianness)

20
user2083050

Polstern und Packen sind nur zwei Aspekte derselben Sache:

  • packung oder Ausrichtung ist die Größe, auf die jedes Mitglied abgerundet wird
  • polsterung ist der zusätzliche Platz, der zur Ausrichtung hinzugefügt wird

In mystruct_A Wird bei einer Standardausrichtung von 4 jedes Element auf ein Vielfaches von 4 Bytes ausgerichtet. Da die Größe von char 1 ist, beträgt der Abstand für a und c 4 - 1 = 3 Bytes, während für int b Kein Abstand erforderlich ist bereits 4 Bytes. Das funktioniert genauso für mystruct_B.

5
casablanca

Strukturpacken wird nur durchgeführt, wenn Sie Ihrem Compiler ausdrücklich anweisen, die Struktur zu packen. Polstern ist das, was Sie sehen. Ihr 32-Bit-System füllt jedes Feld zur Word-Ausrichtung auf. Wenn Sie Ihrem Compiler befohlen hätten, die Strukturen zu packen, wären dies 6 bzw. 5 Bytes. Tu das aber nicht. Es ist nicht portierbar und lässt Compiler viel langsameren (und manchmal sogar fehlerhaften) Code generieren.

1
nmichaels

Es gibt keine Nachteile ! Wer das Thema verstehen will, muss die folgenden Schritte ausführen:

0
snr