Strukturpolsterung und Verpackung

209

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 findet Polsterung oder Verpackung statt?

Manu
quelle
3
Lesen Sie stackoverflow.com/questions/119123/…
Prasoon Saurav
24
Die verlorene Kunst des C-Struktur-Packens - catb.org/esr/structure-packing
Paolo
paddingmacht die Dinge größer. packingmacht die Dinge kleiner. Ganz anders.
Smwikipedia

Antworten:

264

Klotzen ausrichtet Strukturelemente zu „natürlichen“ Adressgrenzen - sagen wir, intwürden Mitglieder Offsets haben, die sich mod(4) == 0auf 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;

Das Packen hingegen verhindert, dass der Compiler ein Padding ausführt - dies muss explizit angefordert werden - unter GCC ist es __attribute__((__packed__))das Folgende:

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

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

Ein Hinweis: Der nicht ausgerichtete Speicherzugriff ist bei Architekturen, die dies zulassen (wie x86 und amd64), langsamer und bei Architekturen mit strikter Ausrichtung wie SPARC ausdrücklich verboten .

Nikolai Fetissov
quelle
2
Ich frage mich: Bedeutet das Verbot eines nicht ausgerichteten Speichers für den Funken, dass er nicht mit üblichen Byte-Arrays umgehen kann? Wie ich weiß, wird das Packen von Strukturen hauptsächlich zum Übertragen (dh Vernetzen) von Daten verwendet, wenn Sie ein Byte-Array in eine Struktur umwandeln und sicherstellen müssen, dass ein Array zu einem Strukturfeld passt. Wenn der Funke das nicht kann, wie arbeiten die überhaupt?!
Hi-Angel
14
Genau aus diesem Grund sehen Sie bei Betrachtung der IP-, UDP- und TCP-Header-Layouts, dass alle ganzzahligen Felder ausgerichtet sind.
Nikolai Fetissov
17
Die "Lost Art of C Structure Packing" erklärt das Auffüllen und Verpacken - catb.org/esr/structure-packing
Rob11311
3
Muss das erste Mitglied zuerst kommen? Ich dachte, die Verwaltung hängt vollständig von der Implementierung ab und kann nicht als verlässlich angesehen werden (auch nicht von Version zu Version).
Allyourcode
4
+ allyourcode Der Standard garantiert, dass die Reihenfolge der Mitglieder erhalten bleibt und das erste Mitglied bei 0 Offset beginnt.
Martinkunev
64

( Die oben genannten Antworten erklärt den Grund , ganz klar, aber es scheint über die Größe der Polsterung nicht ganz klar, so werde ich eine Antwort hinzufügen nach dem , was ich daraus gelernt The Lost Art of Structure Verpackung hat es nicht Grenze entwickelte sich zu C, aber gilt auch für Go, 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.
    Beispiel: Auf einem 64-Bit-System intsollte an einer Adresse beginnen, die durch 4 und longdurch 8 shortdurch 2 teilbar ist .
  • charund char[]sind speziell, könnte jede Speicheradresse sein, so dass sie nicht vor ihnen aufgefüllt werden müssen.
  • Denn structabgesehen von der Ausrichtung für jedes einzelne Element wird 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.
    Beispiel: Wenn das größte Mitglied der Struktur longdurch 8 teilbar ist, intdann durch 4, shortdann durch 2.

Reihenfolge des Mitglieds:

  • Die Reihenfolge der Mitglieder kann sich auf die tatsächliche Größe der Struktur auswirken. Beachten Sie dies. zB haben das stu_cund stu_daus dem folgenden Beispiel die gleichen Elemente, jedoch in unterschiedlicher Reihenfolge, und führen zu einer unterschiedlichen Größe für die beiden Strukturen.

Adresse im Speicher (für struct)

Regeln:

  • 64-Bit-Systemstrukturadresse
    beginnt bei (n * 16)Bytes. ( Im folgenden Beispiel sehen Sie, dass alle gedruckten Hex-Adressen von Strukturen mit enden 0. )
    Grund : Das möglicherweise größte einzelne Strukturelement ist 16 Byte ( long double).
  • (Update) Wenn eine Struktur nur eincharMitgliedenthält, kann ihre Adresse an einer beliebigen Adresse beginnen.

Leerer Raum :

  • Der leere Raum zwischen 2 structs konnte durch nicht-struct Variablen verwendet werden , die in passen könnte.
    B. in test_struct_address()folgenden werden die variablen xbefindet sich zwischen benachbarten struct gund h.
    Unabhängig davon, ob xdeklariert hwird, ändert sich die Adresse nicht. Verwenden Sie xeinfach den leeren Speicherplatz, der gverschwendet wurde.
    Ä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 Adressstart für jede Variable g: d0 x: dc h: e0 y: e8

Geben Sie hier die Bildbeschreibung ein

Eric Wang
quelle
4
"Regeln" machten es tatsächlich sehr deutlich, ich konnte nirgendwo eine einfache Regel finden. Vielen Dank.
Pervez Alam
2
@PervezAlam Das Buch <The Lost Art of C Structure Packing>erklärt die Regeln ziemlich gut, obwohl es etwas länger ist als diese Antwort. Das Buch ist frei online verfügbar: catb.org/esr/structure-packing
Eric Wang
Ich werde es versuchen, übrigens ist es auf Strukturverpackung beschränkt? Nur Kuriositäten, wie mir die Erklärung im Buch gefallen hat.
Pervez Alam
1
@PervezAlam Es ist ein sehr kurzes Buch, das sich hauptsächlich auf Technologien konzentriert, die den Speicherbedarf von c-Programmen verringern. Das Lesen dauert höchstens einige Tage.
Eric Wang
1
@ValidusOculus Ja, dies bedeutet, dass 16 Byte ausgerichtet sind.
Eric Wang
44

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

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

Variablenausrichtung

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, hängt die Art und Weise, wie sie im Speicher ausgerichtet sind, davon ab, wie viele Wörter von der CPU verarbeitet werden müssen. Wenn 4-Byte-Blöcke so ausgerichtet sind, dass sie immer in das Innere eines Blocks passen (die Speicheradresse ist ein Vielfaches von 4), muss nur ein Wort verarbeitet werden. Andernfalls könnte ein Block von 4 Bytes einen Teil von sich selbst in einem Block und einen Teil von einem anderen haben, so dass der Prozessor 2 Wörter verarbeiten muss, um diese Daten zu lesen.

Gleiches 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 in einem Block befindet.

Dies berücksichtigt ein 8-Byte-Textverarbeitungsprogramm, 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 mit diesen Blöcken ausgerichtet sind, wodurch die Leistung beim Lesen des Speichers verbessert wird.

Wie in anderen Antworten angegeben, ist der Raum jedoch manchmal wichtiger als die Leistung selbst. Vielleicht verarbeiten Sie viele Daten auf einem Computer, der nicht viel RAM hat (Swap Space könnte verwendet werden, aber es ist VIEL langsamer). Sie können die Variablen im Programm anordnen, bis das geringste Auffüllen erfolgt ist (wie in einigen anderen Antworten deutlich gezeigt wurde). Wenn dies jedoch nicht ausreicht, können Sie das Auffüllen explizit deaktivieren. Dies ist das Packen .

IanC
quelle
3
Dies erklärt nicht das Packen der Struktur, zeigt jedoch die Ausrichtung der CPU-Wörter sehr gut.
David Foerster
Hast du das in Farbe gezeichnet? :-)
Ciro Santilli 法轮功 冠状 病 六四 事件 30
1
@ CiroSantilli709 it 抓捕 六四 事件 法轮功, es war auf Gimp, aber ich denke, ich hätte einige Zeit gespart, es auf Farbe zu tun, obwohl haha
IanC
1
Noch besser seit Open Source (Y)
Ciro Santilli 30 冠状 病 六四 六四 法轮功
21

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

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

#pragma pack(n)

Beispielsweise stellt ARM das __packedSchlüsselwort zum Unterdrücken des Strukturauffüllens bereit . Lesen Sie Ihr Compiler-Handbuch, um mehr darüber zu erfahren.

Eine gepackte Struktur ist also eine Struktur ohne Polsterung.

Im Allgemeinen werden gepackte Strukturen verwendet

  • Platz sparen

  • Formatieren einer Datenstruktur für die Übertragung über ein Netzwerk mithilfe eines Protokolls (dies ist natürlich keine gute Vorgehensweise, da Sie
    sich mit Endianness befassen müssen).

user2083050
quelle
5

Polsterung und Verpackung sind nur zwei Aspekte derselben Sache:

  • Packung oder Ausrichtung ist die Größe, auf die jedes Element abgerundet ist
  • Polsterung ist der zusätzliche Platz, der hinzugefügt wird, um der Ausrichtung zu entsprechen

Unter der mystruct_AAnnahme einer Standardausrichtung von 4 wird jedes Mitglied auf ein Vielfaches von 4 Bytes ausgerichtet. Da die Größe von char1 ist, beträgt die Auffüllung für aund c4 - 1 = 3 Bytes, während keine Auffüllung erforderlich ist, für int bdie bereits 4 Bytes vorhanden sind. Es funktioniert genauso für mystruct_B.

casablanca
quelle
1

Das Packen von Strukturen erfolgt nur, wenn Sie Ihren Compiler explizit anweisen, die Struktur zu packen. Polsterung ist das, was Sie sehen. Ihr 32-Bit-System füllt jedes Feld mit der Wortausrichtung auf. Wenn Sie Ihrem Compiler gesagt hätten, er solle die Strukturen packen, wären sie 6 bzw. 5 Byte. Tu das aber nicht. Es ist nicht portabel und lässt Compiler viel langsameren (und manchmal sogar fehlerhaften) Code generieren.

nmichaels
quelle
1

Es gibt kein Aber! Wer das Thema erfassen will, muss folgende tun:

snr
quelle
1

Regeln für das Auffüllen:

  1. Jedes Mitglied der Struktur sollte sich an einer Adresse befinden, die durch ihre Größe teilbar ist. Das Auffüllen wird zwischen Elementen oder am Ende der Struktur eingefügt, um sicherzustellen, dass diese Regel erfüllt ist. Dies geschieht für einen einfacheren und effizienteren Buszugriff durch die Hardware.
  2. Das Auffüllen am Ende der Struktur wird basierend auf der Größe des größten Elements der Struktur festgelegt.

Warum Regel 2: Betrachten Sie die folgende Struktur:

Struktur 1

Wenn wir ein Array (mit 2 Strukturen) dieser Struktur erstellen, ist am Ende kein Auffüllen erforderlich:

Struct1-Array

Daher ist die Größe von struct = 8 Bytes

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

Struktur 2

Wenn wir ein Array dieser Struktur erstellen, gibt es zwei Möglichkeiten, die Anzahl der am Ende erforderlichen Auffüllbytes.

A. Wenn wir am Ende 3 Bytes hinzufügen und es für int und nicht 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 an einem Vielfachen des größten Elements (dh wenn wir ein Array dieser Struktur erstellen möchten, muss die erste Adresse des zweiten Arrays an einer Adresse beginnen, die ein Vielfaches ist Hier können wir mit 24 (3 * 8) die Anzahl der am Ende erforderlichen Füllbytes berechnen.

AlphaGoku
quelle
-1

Die Datenstrukturausrichtung ist die Art und Weise, wie Daten im Computerspeicher angeordnet und abgerufen werden. Es besteht aus zwei getrennten, aber verwandten Themen: Datenausrichtung und Auffüllen der Datenstruktur . Wenn ein moderner Computer von einer Speicheradresse liest oder in diese schreibt, geschieht dies in wortgroßen Blöcken (z. B. 4-Byte-Blöcken auf einem 32-Bit-System) oder größer. Datenausrichtung bedeutet, dass die Daten an einer Speicheradresse abgelegt werden, die einem Vielfachen der Wortgröße entspricht. Dies erhöht die Systemleistung aufgrund der Art und Weise, wie die CPU mit dem Speicher umgeht. Um die Daten auszurichten, müssen möglicherweise einige bedeutungslose Bytes zwischen dem Ende der letzten Datenstruktur und dem Beginn der nächsten Daten eingefügt werden. Dies ist das Auffüllen der Datenstruktur.

  1. Um die Daten im Speicher auszurichten, werden ein oder mehrere leere Bytes (Adressen) zwischen Speicheradressen eingefügt (oder leer gelassen), die während der Speicherzuweisung anderen Strukturelementen zugewiesen werden. Dieses Konzept wird als Strukturauffüllung bezeichnet.
  2. Die Architektur eines Computerprozessors ist so, dass er jeweils 1 Wort (4 Byte im 32-Bit-Prozessor) aus dem Speicher lesen kann.
  3. Um diesen Vorteil des Prozessors zu nutzen, werden Daten immer als 4-Byte-Paket ausgerichtet, was dazu führt, dass leere Adressen zwischen die Adressen anderer Mitglieder eingefügt werden.
  4. Aufgrund dieses Strukturauffüllungskonzepts in C entspricht die Größe der Struktur immer nicht unserer Meinung nach.
Manoj Yadav
quelle
1
Warum müssen Sie in Ihrer Antwort fünfmal auf denselben Artikel verlinken ? Bitte behalten Sie nur einen Link zum Beispiel. Da Sie auf Ihren Artikel verlinken, müssen Sie diese Tatsache offenlegen.
Artjom B.