Warum Structs Löcher haben
Jeder skalare Typ hat ein Alignment: Auf den meisten ABIs muss ein 4-Byte-int an einer durch 4 teilbaren Adresse liegen, ein 8-Byte-double oder Zeiger an einer durch 8 teilbaren. Der Compiler fügt vor einem Member Padding ein, um dessen Alignment zu erreichen, und am Ende, damit Arrays des Structs jedes Element ausgerichtet halten (das Alignment des Structs ist das seines am stärksten ausgerichteten Members). struct { char c; int i; } ist daher 8 Byte groß, nicht 5, und struct { char a; long b; char c; } hat 24 Byte, während { long b; char a; char c; } 16 hat. Member vom größten zum kleinsten Alignment zu ordnen entfernt das meiste Padding; genau das tut der Umsortierungsvorschlag.
Was sich zwischen ABIs unterscheidet
| Typ | x86-64 SysV | AArch64 | i386 | ARM32 | MSVC x64 |
|---|---|---|---|---|---|
| long | 8 | 8 | 4 | 4 | 4 |
| pointer | 8 | 8 | 4 | 4 | 8 |
| double | 8, Align 8 | 8, Align 8 | 8, Align 4 | 8, Align 8 | 8, Align 8 |
| long long | Align 8 | Align 8 | Align 4 | Align 8 | Align 8 |
| long double | 16, Align 16 | 16, Align 16 | 12, Align 4 | 8 | 8 |
| wchar_t | 4 | 4 | 4 | 4 | 2 |
| Bitfeld-Regeln | Itanium: Ein Feld wird am nächsten Bit platziert, außer es würde ein Fenster von sizeof(type) Bytes überschreiten; unbenannte Felder beeinflussen das Alignment nicht (x86) oder doch (ARM) | jede Folge gleich großer Felder bekommt eine eigene Speichereinheit; ein Typwechsel beginnt eine neue | |||
Derselbe Quelltext kann daher auf verschiedenen Zielen unterschiedliche Binärlayouts ergeben, weshalb Structs, die über das Netz oder in Dateien gehen, Typen fester Breite, explizite Padding-Member und static_assert(sizeof(struct x) == 24) verwenden sollten.
Bitfelder
Benachbarte Bitfelder werden in die Speichereinheit ihres deklarierten Typs gepackt. Bei GCC und Clang kommt ein Feld an das nächste freie Bit, außer es würde ein sizeof(type)-Byte-Fenster überspannen; dann rückt es an die nächste Alignment-Grenze. Ein Feld der Breite null (int :0) zwingt das nächste Feld in eine neue Einheit. MSVC legt eine neue Einheit an, sobald sich die Typgröße ändert, sodass int a:8; char b:8; auf MSVC 5 Byte belegt und auf GCC 2. Die Bitreihenfolge innerhalb der Einheit folgt der Endianness des Ziels (niedrigstwertige Bits zuerst bei Little-Endian), weshalb Bitfelder ein schlechtes Mittel sind, um Wire-Formate zu beschreiben.
packed, aligned und #pragma pack
__attribute__((packed)) entfernt alles Padding (Member können falsch ausgerichtet sein, was auf x86 langsam ist und auf manchen ARM-Konfigurationen einen Fault auslöst). #pragma pack(n) begrenzt das Member-Alignment auf n Byte. __attribute__((aligned(n))) und _Alignas(n) erhöhen das Alignment, typischerweise auf 16 für SIMD oder 64 für eine Cache-Zeile. All das wird im Layout gezeigt.
Fragen
Wie genau ist das?
Die Engine wird gegen clang -fdump-record-layouts für x86-64, i386, AArch64, ARM32 und MSVC x64 an einer Reihe von Structs mit Bitfeldern, Packing, verschachtelten Unions und Feldern der Breite null getestet. GCC und Clang stimmen auf diesen ABIs überein. Exotische Fälle (überausgerichtete Typen in Bitfeldern, __int128 auf 32 Bit, Vektortypen) prüfen Sie mit dem Compiler.
Wie bekomme ich die echten Werte von meinem Compiler?
clang -Xclang -fdump-record-layouts -c datei.c gibt jedes Struct aus; GCC: mit -g kompilieren und pahole auf das Objekt anwenden, oder offsetof() und sizeof() in einem kleinen Programm verwenden. Mit static_assert(offsetof(struct s, x) == 8) frieren Sie ein Layout ein.
Es meldet einen unbekannten Typ.
Typen aus System-Headern (size_t, uint32_t, time_t und die Windows-DWORD-Familie) sind eingebaut; alles andere braucht sein typedef über dem Struct, das es verwendet. Ein unbekannter Name wird als 4-Byte-int behandelt und markiert.
Kann es ein Struct aus einem Core-Dump oder Paket dekodieren?
Fügen Sie die Bytes im Dekodier-Abschnitt ein. Ganzzahlen, Floats, chars, Zeiger, char-Arrays und Bitfelder werden in der gewählten Bytereihenfolge gezeigt; verschachtelte Structs werden aufgelistet, aber nicht aufgeklappt (wählen Sie das innere Struct, um es separat zu dekodieren).
Warum hilft das Umsortieren bei meinem Struct nicht?
Wenn die Member bereits nach Alignment sortiert sind oder das Struct Bitfelder oder anonyme Unions hat (die nicht automatisch umsortiert werden), wird der Vorschlag ausgeblendet. End-Padding ist unvermeidlich, wenn das Alignment des größten Members die Summe der Größen nicht teilt.