Strukturpolsterung und -verpackung

Lesezeit: 15 Minuten

Benutzeravatar von Manu
Manu

In Betracht ziehen:

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

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

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

Sind diese Strukturen gepolstert oder verpackt?

Wann wird gepolstert oder verpackt?

  • Lesen Sie stackoverflow.com/questions/119123/…

    – Prasun Saurav

    29. November 2010 um 17:18 Uhr

  • Die verlorene Kunst des C-Struktur-Packens – catb.org/esr/structure-packing

    – Paulo

    6. April 2016 um 10:34 Uhr

  • padding macht die Sache größer. packing macht die Sache kleiner. Ganz anders.

    – smWikipedia

    25. Juni 2017 um 2:45 Uhr

  • @Paolo, dieser Lost Art-Link zeigt nicht, was passiert, wenn es eine Zeigerausrichtung gibt und oben, wo zwei ints nacheinander sein könnten.

    – mlSchüler33

    10. Juli 2020 um 19:37 Uhr

  • Verwandte, für C++: stackoverflow.com/questions/44287060/…

    – Gabriel Staples

    17. September 2020 um 16:20 Uhr

Benutzeravatar von Nikolai Fetissov
Nikolai Fetissov

Polsterung richtet sich aus strukturieren Sie Mitglieder auf “natürliche” Adressgrenzen – sagen wir, int Mitglieder hätten Offsets, die sind mod(4) == 0 auf 32-Bit-Plattform. Die Polsterung 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;

Verpackungverhindert andererseits, dass der Compiler auffüllt – dies muss explizit angefordert werden – unter GCC ist es __attribute__((__packed__))also folgendes:

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

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

Ein Hinweis jedoch: Nicht ausgerichteter Speicherzugriff ist auf Architekturen, die dies zulassen (wie x86 und amd64), langsamer und ausdrücklich verboten Strenge Alignment-Architekturen wie SPARC.

  • Ich frage mich: Bedeutet das Verbot von nicht ausgerichtetem Speicher auf dem Funken, dass er nicht mit üblichen Byte-Arrays umgehen kann? Struct Packing wird, wie ich weiß, hauptsächlich beim Übertragen (dh Vernetzen) von Daten verwendet, wenn Sie ein Byte-Array in eine Struktur umwandeln und sicherstellen müssen, dass ein Array zu einem Struct-Feld passt. Wenn der Funke das nicht kann, wie arbeiten die dann überhaupt?!

    – Hallo Engel

    24. Mai 2014 um 9:19 Uhr

  • Genau aus diesem Grund sehen Sie, wenn Sie sich IP-, UDP- und TCP-Header-Layouts ansehen, dass alle Integer-Felder ausgerichtet sind.

    – Nikolai Fetissov

    24. Mai 2014 um 15:09 Uhr

  • Die “Lost Art of C Structure Packing” erklärt Padding- und Packing-Optimierungen – catb.org/esr/structure-packing

    – Rob11311

    29. Juni 2014 um 16:45 Uhr

  • Muss das erste Mitglied zuerst kommen? Ich dachte, die Anordnung hängt ganz von der Implementierung ab und kann sich nicht darauf verlassen (auch nicht von Version zu Version).

    – Allourcode

    30. Juli 2015 um 1:23 Uhr

  • +allyourcode Der Standard garantiert, dass die Reihenfolge der Mitglieder erhalten bleibt und das erste Mitglied bei 0 Offset beginnt.

    – Martinkunew

    8. Dezember 2017 um 14:32 Uhr

Benutzeravatar von Eric
Erich

(Die obigen Antworten haben den Grund ziemlich klar erklärt, scheinen aber die Größe der Polsterung nicht ganz klar zu machen, also werde ich eine Antwort hinzufügen, die dem entspricht, was ich gelernt habe Die verlorene Kunst der Strukturverpackunges hat sich entwickelt, um nicht darauf beschränkt zu sein Caber auch anwendbar auf Go, Rust.)


Speicherausrichtung (für Struct)

Regeln:

  • Vor jedem einzelnen Mitglied wird aufgefüllt, damit es an einer Adresse beginnt, die durch seine Ausrichtungsanforderung teilbar ist.
    ZB auf vielen Systemen, an int sollte bei einer Adresse beginnen, die durch 4 und a teilbar ist short um 2.
  • char und char[] sind etwas Besonderes, könnten jede Speicheradresse sein, also müssen sie nicht vor ihnen aufgefüllt werden.
  • Zum structAbgesehen von der Notwendigkeit der Ausrichtung für jedes einzelne Mitglied wird die Größe der gesamten Struktur selbst auf eine Größe ausgerichtet, die durch die strengste Ausrichtungsanforderung eines ihrer Mitglieder teilbar ist, indem sie am Ende aufgefüllt wird.
    ZB auf vielen Systemen, wenn das größte Mitglied von struct ist int dann durch durch 4 teilbar, wenn short dann um 2.

Reihenfolge der Mitglieder:

  • Die Reihenfolge der Mitglieder kann sich auf die tatsächliche Größe der Struktur auswirken, also denken Sie daran. Bsp. die stu_c und stu_d aus dem Beispiel unten haben die gleichen Mitglieder, aber in unterschiedlicher Reihenfolge, und führen zu unterschiedlichen Größen für die 2 Strukturen.

Adresse im Speicher (für Struct)

Freiraum:

  • Leerraum zwischen 2 Structs könnte von Nicht-Struct-Variablen verwendet werden, die hineinpassen könnten.
    zB hinein test_struct_address() unten die Variable x befindet sich zwischen benachbarten struct g und h.
    Ganz gleich, ob x erklärt wird, hDie Adresse von ändert sich nicht, x nur den leeren Raum wiederverwendet, dass g verschwendet.
    Ähnlicher Fall 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 Adressstart für jede Variable g:d0 x:dc h:e0 y:e8

Geben Sie hier die Bildbeschreibung ein

  • “Regeln” hat es eigentlich sehr deutlich gemacht, ich konnte nirgendwo eine einfache Regel finden. Vielen Dank.

    – Pervez Alam

    14. Februar 2017 um 9:11 Uhr

  • @PervezAlam Das Buch <The Lost Art of C Structure Packing>, erklärt die Regeln ziemlich gut, auch wenn es etwas länger ist als diese Antwort. Das Buch ist online frei verfügbar: catb.org/esr/structure-packing

    – Erich

    14. Februar 2017 um 12:42 Uhr


  • @PervezAlam Es ist ein sehr kurzes Buch, das sich hauptsächlich auf Technologien konzentriert, die den Speicherbedarf des C-Programms reduzieren würden. Es dauert höchstens mehrere Tage, bis das Lesen abgeschlossen ist.

    – Erich

    14. Februar 2017 um 13:29 Uhr


  • @AkshayImmanuelD Ja, jedes einzelne Mitglied befolgt Regel 1, und die Startadresse des ersten Mitglieds ist auch die Startadresse der Struktur selbst.

    – Erich

    6. Februar 2018 um 12:15 Uhr

  • @ValidusOculus Ja, es bedeutet 16 Byte ausgerichtet.

    – Erich

    29. Oktober 2018 um 6:55 Uhr


Benutzeravatar von IanC
IanC

Ich weiß, dass diese Frage alt ist und die meisten Antworten hier das Auffüllen sehr gut erklären, aber als ich versuchte, es selbst zu verstehen, dachte ich, dass es hilfreich ist, ein “visuelles” Bild von dem zu haben, was passiert.

Der Prozessor liest den Speicher in “Blöcken” einer bestimmten Größe (Wort). Angenommen, das Prozessorwort ist 8 Bytes lang. Es betrachtet den Speicher als eine große Reihe von 8-Byte-Bausteinen. Jedes Mal, wenn es Informationen aus dem Speicher abrufen muss, erreicht es einen dieser Blöcke und erhält sie.

Variablenausrichtung

Wie im Bild oben zu sehen ist, spielt es keine Rolle, wo sich ein Char (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 umgehen, die größer als ein Byte sind, wie ein 4-Byte-Int oder ein 8-Byte-Double, macht die Art und Weise, wie sie im Speicher ausgerichtet sind, einen Unterschied, 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 (Speicheradresse ist ein Vielfaches von 4), muss nur ein Wort verarbeitet werden. Andernfalls könnte ein Teil von 4 Bytes einen Teil von sich selbst in einem Block und einen Teil in einem anderen haben, sodass 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 einer Speicheradresse befinden muss, die ein Vielfaches von 8 ist, um sicherzustellen, dass es sich immer innerhalb eines Blocks befindet.

Dies betrachtet ein 8-Byte-Textverarbeitungsprogramm, aber das Konzept gilt für andere Wortgrößen.

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

Wie in anderen Antworten angegeben, ist der Platz jedoch manchmal wichtiger als die Leistung selbst. Vielleicht verarbeiten Sie viele Daten auf einem Computer, der nicht über viel RAM verfügt (Auslagerungsspeicher könnte verwendet werden, ist aber VIEL langsamer). Sie könnten die Variablen im Programm anordnen, bis die geringste Auffüllung erfolgt ist (wie in einigen anderen Antworten stark veranschaulicht wurde), aber wenn dies nicht ausreicht, können Sie die Auffüllung explizit deaktivieren, was was ist Verpackung ist.

  • Dies erklärt nicht das Strukturpacken, aber es veranschaulicht die CPU-Wortausrichtung recht gut.

    – David Förster

    7. August 2016 um 23:49 Uhr

  • Hast du das mit Farbe gezeichnet? 🙂

    – Ciro Santilli OurBigBook.com

    30. Juli 2017 um 10:58 Uhr

  • @CiroSantilli709大抓捕六四事件法轮功, es war auf Gimp, aber ich denke, ich hätte etwas Zeit gespart, wenn ich es mit Farbe gemacht hätte, haha

    – IanC

    30. Juli 2017 um 22:21 Uhr

  • Noch besser seit Open Source (Y)

    – Ciro Santilli OurBigBook.com

    30. Juli 2017 um 22:25 Uhr

  • Es ist auch sehr praktisch, wenn Strukturen aus Dateien gelesen werden. Man kann dann einfach in einen Puffer einlesen memcpy usw. direkt in eine Struktur.

    – Benutzer3342816

    19. Juli 2021 um 3:15 Uhr

Benutzeravatar von user2083050
Benutzer2083050

Das Strukturpacken unterdrückt das Strukturpolster, das Polstern wird verwendet, wenn die Ausrichtung am wichtigsten ist, das Packen wird verwendet, wenn der Platz am wichtigsten ist.

Einige Compiler bieten #pragma um das Auffüllen zu unterdrücken oder um es auf eine Anzahl von n Bytes zu packen. Einige bieten dafür Schlüsselwörter an. Im Allgemeinen hat das Pragma, das zum Ändern der Strukturauffüllung verwendet wird, das folgende Format (abhängig vom Compiler):

#pragma pack(n)

Beispielsweise stellt ARM die __packed Schlüsselwort, um das Auffüllen von Strukturen zu unterdrücken. Gehen Sie Ihr Compiler-Handbuch durch, 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

  • um eine Datenstruktur für die Übertragung über ein Netzwerk mit einem Protokoll zu formatieren (dies ist natürlich keine gute Praxis, da Sie dies tun müssen
    Umgang mit Endianness)

Polsterung und Verpackung 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 hinzugefügt wird, um der Ausrichtung zu entsprechen

Im mystruct_A, unter der Annahme einer Standardausrichtung von 4, wird jedes Mitglied auf ein Vielfaches von 4 Bytes ausgerichtet. Da die Größe von char ist 1, die Polsterung für a und c ist 4 – 1 = 3 Bytes, während kein Auffüllen erforderlich ist int b das sind schon 4 bytes. Es funktioniert genauso für mystruct_B.

Regeln für die Polsterung:

  1. Jedes Mitglied der Struktur sollte sich an einer Adresse befinden, die durch seine Größe teilbar ist. Padding wird zwischen Elementen oder am Ende der Struktur eingefügt, um sicherzustellen, dass diese Regel eingehalten wird. Dies erfolgt für einen einfacheren und effizienteren Buszugriff durch die Hardware.
  2. Die Auffüllung am Ende der Struktur wird basierend auf der Größe des größten Mitglieds der Struktur festgelegt.

Warum Regel 2: Betrachten Sie die folgende Struktur,

Struktur 1

Wenn wir ein Array (von 2 Strukturen) dieser Struktur erstellen würden, ist am Ende keine Auffüllung erforderlich:

Struct1-Array

Daher ist die Größe der Struktur = 8 Bytes

Angenommen, wir würden eine weitere Struktur wie folgt erstellen:

Struktur 2

Wenn wir ein Array dieser Struktur erstellen würden, gibt es zwei Möglichkeiten für die Anzahl der am Ende erforderlichen Bytes zum Auffüllen.

A. Wenn wir am Ende 3 Bytes hinzufügen und es für int und nicht für Long ausrichten:

Struct2-Array ausgerichtet auf int

B. Wenn wir am Ende 7 Bytes hinzufügen und für Long ausrichten:

Struct2-Array auf Long ausgerichtet

Die Startadresse des zweiten Arrays ist ein Vielfaches von 8 (dh 24). Die Größe der Struktur = 24 Bytes

Durch Ausrichten der Startadresse des nächsten Arrays der Struktur auf ein Vielfaches des größten Elements (dh wenn wir ein Array dieser Struktur erstellen würden, muss die erste Adresse des zweiten Arrays an einer Adresse beginnen, die ein Vielfaches ist des größten Mitglieds der Struktur, hier 24(3 * 8)), können wir die Anzahl der Füllbytes berechnen, die am Ende benötigt werden.

Die Variablen werden an beliebigen Adressen gespeichert, die durch ihre Ausrichtung (im Allgemeinen durch ihre Größe) teilbar sind. Das Auffüllen/Verpacken dient also nicht nur der Struktur. Eigentlich, Alle Daten haben ihre eigene Ausrichtungsanforderung:

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.

Dies ist ähnlich wie struct, aber es gibt einige Unterschiede. Erstens können wir sagen, dass es zwei Arten von Padding gibt – a) Um jedes Member richtig von seiner Adresse zu starten, werden einige Bytes zwischen Members eingefügt. b) Um die nächste Struct-Instanz ordnungsgemäß von ihrer Adresse aus zu starten, werden einige Bytes an jede Struct angehängt:

// 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.
};
  1. Das erste Mitglied der Struktur beginnt immer mit allen Adressen, die durch die eigene Ausrichtungsanforderung der Struktur teilbar sind, die durch die Ausrichtungsanforderung des größten Mitglieds bestimmt wird (hier 4Ausrichtung von int32_t). Bei normalen Variablen ist das anders. Die normalen Variablen können beliebige Adressen beginnen, die durch ihre Ausrichtung teilbar sind, aber das ist nicht der Fall für das erste Mitglied von struct. Wie Sie wissen, ist die Adresse einer Struktur dieselbe wie die Adresse ihres ersten Elements.
  2. Innerhalb einer Struktur können zusätzliche aufgefüllte nachlaufende Bytes vorhanden sein, wodurch die nächste Struktur (oder das nächste Element in einem Array von Strukturen) von ihrer eigenen Adresse aus gestartet wird. Denk an struct st arr[2];. Zu machen arr[1](arr[1]‘s erstes Mitglied) beginnend mit einer Adresse, die durch 4 teilbar ist, sollten wir 3 Bytes an das Ende jeder Struktur anhängen.

Daraus habe ich gelernt Die verlorene Kunst der Strukturverpackung.

HINWEIS: Sie können untersuchen, was die Ausrichtungsanforderung des Datentyps ist _Alignof Operator. Außerdem können Sie den Offset des Mitglieds innerhalb einer Struktur durch abrufen offsetof Makro.

1426790cookie-checkStrukturpolsterung und -verpackung

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy