bitlvl
IT

Visualizzatore di layout di struct C e campi di bit

Incolla una o più definizioni di struct, union, enum o typedef. Ogni struct viene disegnata byte per byte con il suo padding, per l’ABI scelta, esattamente come la disporrebbero GCC, Clang o MSVC. Poi incolla un dump hex per vedere i valori dei campi.

Decodifica un dump hex in questa struct
Ordine dei byte

I risultati e i messaggi di questo strumento sono mostrati in inglese.

Perché le struct hanno dei buchi

Ogni tipo scalare ha un allineamento: nella maggior parte delle ABI, un int da 4 byte deve stare a un indirizzo divisibile per 4, un double o un puntatore da 8 byte a uno divisibile per 8. Il compilatore inserisce padding prima di un membro per raggiungere il suo allineamento, e alla fine perché gli array della struct mantengano allineato ogni elemento (l’allineamento della struct è quello del membro più allineato). Per questo struct { char c; int i; } occupa 8 byte, non 5, e struct { char a; long b; char c; } ne occupa 24 mentre { long b; char a; char c; } ne occupa 16. Ordinare i membri dall’allineamento maggiore al minore elimina la maggior parte del padding; è proprio quello che fa il suggerimento di riordino.

Cosa cambia tra le ABI

Tipox86-64 SysVAArch64i386ARM32MSVC x64
long88444
pointer88448
double8, allineamento 88, allineamento 88, allineamento 48, allineamento 88, allineamento 8
long longallineamento 8allineamento 8allineamento 4allineamento 8allineamento 8
long double16, allineamento 1616, allineamento 1612, allineamento 488
wchar_t44442
Regole dei campi di bitItanium: un campo va nel bit successivo a meno che attraversi una finestra di sizeof(type) byte; i campi senza nome non influiscono sull’allineamento (x86) oppure sì (ARM)ogni sequenza di campi della stessa dimensione ha la propria unità di memoria; un cambio di tipo ne inizia una nuova

Lo stesso sorgente può dare layout binari diversi su target diversi, per questo le struct che viaggiano in rete o finiscono in file dovrebbero usare tipi a larghezza fissa, membri di padding espliciti e static_assert(sizeof(struct x) == 24).

Campi di bit

I campi di bit adiacenti vengono impacchettati nell’unità di memoria del tipo dichiarato. In GCC e Clang un campo va nel primo bit libero a meno che attraversi una finestra di sizeof(type) byte; in quel caso salta al confine di allineamento successivo. Un campo di larghezza zero (int :0) costringe il campo successivo a iniziare una nuova unità. MSVC apre una nuova unità appena cambia la dimensione del tipo, quindi int a:8; char b:8; occupa 5 byte su MSVC e 2 su GCC. L’ordine dei bit dentro l’unità segue l’endianness del target (bit meno significativi per primi in little-endian), ed è per questo che i campi di bit sono un pessimo modo di descrivere formati di rete.

packed, aligned e #pragma pack

__attribute__((packed)) rimuove tutto il padding (i membri possono risultare disallineati, cosa lenta su x86 e causa di fault su alcune configurazioni ARM). #pragma pack(n) limita l’allineamento dei membri a n byte. __attribute__((aligned(n))) e _Alignas(n) aumentano l’allineamento, tipicamente a 16 per SIMD o 64 per una linea di cache. Tutto questo è mostrato nel layout.

Domande

Quanto è preciso?

Il motore è testato contro clang -fdump-record-layouts per x86-64, i386, AArch64, ARM32 e MSVC x64 su una serie di struct con campi di bit, packing, union annidate e campi di larghezza zero. GCC e Clang concordano su quelle ABI. I casi esotici (tipi sovra-allineati nei campi di bit, __int128 a 32 bit, tipi vettoriali) verificali con il compilatore.

Come ottengo i valori reali dal mio compilatore?

clang -Xclang -fdump-record-layouts -c file.c stampa ogni struct; GCC: compila con -g ed esegui pahole sull’oggetto, oppure usa offsetof() e sizeof() in un piccolo programma. Con static_assert(offsetof(struct s, x) == 8) congeli un layout.

Dice che c’è un tipo sconosciuto.

I tipi degli header di sistema (size_t, uint32_t, time_t e la famiglia DWORD di Windows) sono integrati; tutto il resto ha bisogno del suo typedef sopra la struct che lo usa. Un nome sconosciuto viene trattato come int da 4 byte e segnalato.

Può decodificare una struct da un core dump o da un pacchetto?

Incolla i byte nella sezione di decodifica. Interi, float, char, puntatori, array di char e campi di bit vengono mostrati nell’ordine dei byte scelto; le struct annidate vengono elencate ma non espanse (scegli la struct interna per decodificarla a parte).

Perché il riordino non aiuta con la mia struct?

Se i membri sono già ordinati per allineamento o la struct ha campi di bit o union anonime (che non vengono riordinate automaticamente), il suggerimento è nascosto. Il padding finale è inevitabile quando l’allineamento del membro più grande non divide la somma delle dimensioni.