Nachlaufendes Padding in C/C++ in verschachtelten Strukturen – ist es notwendig?

Lesezeit: 5 Minuten

Benutzeravatar von Dom324
Dom324

Das ist eher eine theoretische Frage. Ich bin mit der Funktionsweise von Padding und Trailing Padding vertraut.

struct myStruct{
    uint32_t x;
    char*    p;
    char     c;
};

// myStruct layout will compile to
// x:       4 Bytes
// padding: 4 Bytes
// *p:      8 Bytes
// c:       1 Byte
// padding: 7 Bytes
// Total:   24 Bytes

Danach muss noch gepolstert werden xdamit *p ausgerichtet ist, und danach muss eine nachlaufende Polsterung vorhanden sein c sodass die gesamte Strukturgröße durch 8 teilbar ist (um die richtige Schrittlänge zu erhalten). Aber betrachten Sie dieses Beispiel:

struct A{
    uint64_t x;
    uint8_t  y;
};

struct B{
    struct A myStruct;
    uint32_t c;
};

// Based on all information I read on internet, and based on my tinkering
// with both GCC and Clang, the layout of struct B will look like:
// myStruct.x:       8 Bytes
// myStruct.y:       1 Byte
// myStruct.padding: 7 Bytes
// c:                4 Bytes
// padding:          4 Bytes
// total size:       24 Bytes
// total padding:    11 Bytes
// padding overhead: 45%

// my question is, why struct A does not get "inlined" into struct B,
// and therefore why the final layout of struct B does not look like this:
// myStruct.x:       8 Bytes
// myStruct.y:       1 Byte
// padding           3 Bytes
// c:                4 Bytes
// total size:       16 Bytes
// total padding:    3 Bytes
// padding overhead: 19%

Beide Layouts erfüllen die Ausrichtungen aller Variablen. Beide Layouts haben die gleiche Reihenfolge der Variablen. In beiden Layouts struct B hat die richtige Schrittlänge (teilbar durch 8 Bytes). Der einzige Unterschied (neben 33 % kleinerer Größe) ist das struct A hat in Layout 2 keine korrekte Schrittlänge, aber das sollte keine Rolle spielen, da es eindeutig kein Array von gibt struct As.

Ich habe dieses Layout in GCC mit -O3 und -g überprüft, struct B hat 24 Bytes.

Meine Frage ist – gibt es einen Grund, warum diese Optimierung nicht angewendet wird? Gibt es eine Layoutanforderung in C/C++, die dies verbietet? Oder fehlt mir ein Compilation-Flag? Oder ist das ein ABI-Ding?

EDIT: Beantwortet.

  1. Siehe Antwort von @dbush, warum der Compiler dieses Layout nicht selbst ausgeben kann.
  2. Das folgende Codebeispiel verwendet GCC-Pragmas packed und aligned (wie von @jaskij vorgeschlagen), um das optimiertere Layout manuell zu erzwingen. Struktur B_packed hat nur 16 Bytes statt 24 Bytes (beachten Sie, dass dieser Code Probleme verursachen kann/langsam läuft, wenn ein Array von Strukturen vorhanden ist B_packedseien Sie sich bewusst und kopieren Sie diesen Code nicht blind):
struct __attribute__ ((__packed__)) A_packed{
    uint64_t x;
    uint8_t  y;
};

struct __attribute__ ((__packed__)) B_packed{
    struct A_packed myStruct;
    uint32_t c __attribute__ ((aligned(4)));
};

// Layout of B_packed will be
// myStruct.x:       8 Bytes
// myStruct.y:       1 Byte
// padding for c:    3 Bytes
// c:                4 Bytes
// total size:       16 Bytes
// total padding:    3 Bytes
// padding overhead: 19%

  • Punkt genommen. Ich habe die Frage mit bearbeitet Bytes statt Suffix B. Das Suffix ganz wegzulassen wäre meiner Meinung nach viel verwirrender und nicht richtig.

    – Dom324

    22. Dezember 2022 um 1:05 Uhr

  • Nun, das ist Ansichtssache, denke ich. sizeof gibt immer einen Wert zurück in Bytewenn wir also über Größen von Datentypen, Polsterung usw. sprechen, sind wir es stets Apropos Byte.

    – Reisfeld

    22. Dezember 2022 um 1:06 Uhr

  • Das ist wohl ein Überbleibsel vom Studium der Elektrotechnik an der Uni, eine Zahl ohne Einheit daneben zu sehen, kommt mir wie eine Sünde vor

    – Dom324

    22. Dezember 2022 um 1:32 Uhr

  • Was passiert, wenn Sie ein Array Ihres Typs erstellen? Stellen Sie sich einen Typ vor, der ein 4-Byte hat int gefolgt von einem 1 Byte charwas passiert, wenn Sie diesen Typ in ein Array einfügen?

    – CoffeeTableEspresso

    22. Dezember 2022 um 2:32 Uhr

  • Beachten Sie, dass alle Ihre Größenannahmen für die 64-Bit-Architektur gelten (vielleicht speziell für x76_64). 32 Bit ist noch nicht tot und wird es noch einige Jahre geben. Verdammt, stellenweise wird 16-Bit-Zeug verwendet. Das heißt, vielleicht ist es hier eine Regel, einfach von einem modernen Laptop/Desktop/Server-Prozessor auszugehen? Was du eigentlich anschauen will ist das (etwas verfluchte) packed Attribut. Ich sage verflucht, weil es zu unausgerichtetem Zugriff führt. Obwohl Sie koppeln können packed und aligned um jede gewünschte Ausrichtung zu spezifizieren.

    – jaskij

    22. Dezember 2022 um 21:08 Uhr

Benutzeravatar von dbush
dbusch

Gibt es einen Grund, warum diese Optimierung nicht angewendet wird?

Wenn dies erlaubt wäre, wäre der Wert von sizeof(struct B) wäre zweideutig.

Angenommen, Sie haben Folgendes getan:

struct B b;
struct A a = { 1, 2 };
b.c = 0x12345678;
memcpy(&b.myStruct, &a, sizeof(struct A));

Sie würden den Wert von überschreiben b.c.

  • Richtig, habe nicht über die manuelle Speicherverwaltung nachgedacht. Aber theoretisch wäre diese Optimierung sicher, wenn der Compiler beweisen könnte, dass dies nicht passieren wird, oder?

    – Dom324

    22. Dezember 2022 um 1:16 Uhr

  • Der Compiler kann nicht beweisen, dass dies nicht passieren wird. Lesen Sie über die Halteproblem.

    – Reisfeld

    22. Dezember 2022 um 1:50 Uhr

  • @ Dom324 wenn struct a selbst in einer Datei definiert ist, kann der Compiler das nicht wissen struct b wurde in einer anderen unabhängigen Datei definiert.

    – dbusch

    22. Dezember 2022 um 2:10 Uhr

  • Ich würde wirklich empfehlen, darüber zu lesen Satz von Rice direkt anstelle des Halteproblems. Der Compiler kann entscheiden Sie sich manchmal dafür, die Struktur anders anzulegen oder sie überhaupt nicht in den Speicher zu legen, z wenn es keine manuelle Speicherverwaltung gibt und es sehr kurzlebig ist und es keine gibt struct überhaupt. Allerdings wird man das aufgrund der “Als-Ob”-Regel in keiner Weise aus dem Programm heraus erkennen können.

    – Yeputons

    22. Dezember 2022 um 3:30 Uhr


  • @yeputons: Die Optimierung, von der Sie sprechen, ist ein Fall von skalarem Austausch von Aggregaten, kurz SROA oder SRA. Es ändert nicht wirklich das Struct-Layout, es optimiert die Struktur vollständig und arbeitet nur mit den Mitgliedern. GCC kann dies sogar zwischen Funktionen tun, wie im Papier von 2010 beschrieben Der neue intraprozedurale skalare Ersatz von Aggregatenwie -fipa-sra Optimierungsmöglichkeit. (Nur ein zufälliger Google-Treffer für “skalares Ersetzen von Aggregaten”). Java- und Javascript-JITs tun dies ebenfalls.

    – Peter Cordes

    22. Dezember 2022 um 4:17 Uhr

Polsterung wird verwendet, um die Ausrichtung zu erzwingen. Wenn Sie nun ein Array von struct myStruct haben, dann gibt es eine Regel, dass Array-Elemente ohne Auffüllung aufeinander folgen. In Ihrem Fall wäre ohne Auffüllen innerhalb von myStruct nach dem letzten Feld das zweite myStruct in einem Array nicht richtig ausgerichtet. Daher ist es notwendig, dass sizeof(myStruct) ein Vielfaches der Ausrichtung von myStruct ist, und dafür benötigen Sie möglicherweise genügend Polsterung am Ende.

  • Ja, aber wie Sie sehen können, enthält Struct B kein Array von Struct A (myStructs), daher ist dies irrelevant. Ich spreche von einem Sonderfall, wenn Sie eine Struktur in einer Struktur haben, und es sich nicht um ein Array handelt, und Sie daher keine nachfolgende Auffüllung benötigen, um die richtige Ausrichtung der nächsten Struktur zu erzwingen.

    – Dom324

    24. Dezember 2022 um 23:49 Uhr

1437630cookie-checkNachlaufendes Padding in C/C++ in verschachtelten Strukturen – ist es notwendig?

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

Privacy policy