C / C ++: ordine e allineamento dei campi dei bit di forza

Ho letto che l’ordine dei campi di bit all’interno di una struttura è specifico della piattaforma. Che dire se utilizzo diverse opzioni di imballaggio specifiche del compilatore, i dati di questa garanzia saranno archiviati nell’ordine corretto man mano che vengono scritti? Per esempio:

struct Message { unsigned int version : 3; unsigned int type : 1; unsigned int id : 5; unsigned int data : 6; } __attribute__ ((__packed__)); 

Su un processore Intel con il compilatore GCC, i campi venivano disposti in memoria come vengono mostrati. Message.version era i primi 3 bit nel buffer e Message.type seguiva. Se trovo le opzioni di imballaggio struct equivalenti per vari compilatori, questo sarà multipiattaforma?

No, non sarà completamente portatile. Le opzioni di imballaggio per le strutture sono estensioni e non sono completamente portatili. In aggiunta a ciò, C99 §6.7.2.1, paragrafo 10 dice: “L’ordine di allocazione dei campi di bit all’interno di un’unità (ordine alto a basso ordine o basso ordine a alto ordine) è definito dall’implementazione.”

Anche un singolo compilatore potrebbe porre il campo di bit in modo diverso a seconda della endianità della piattaforma di destinazione, per esempio.

I campi di bit variano ampiamente dal compilatore al compilatore, mi dispiace.

Con GCC, le macchine big endian distribuiscono i bit big end prima e le macchine little endian disegnano i bit poco prima.

K & R dice “I membri del campo adiacente [bit-] delle strutture sono impacchettati in unità di memoria dipendenti dall’implementazione in una direzione dipendente dall’implementazione .. Quando un campo che segue un altro campo non si adatta … può essere diviso tra unità o l’unità può essere Un campo senza nome di larghezza 0 forza questo riempimento … ”

Pertanto, se hai bisogno di un layout binario indipendente dalla macchina, devi farlo da solo.

Quest’ultima affermazione si applica anche ai non bitfield dovuti al padding, tuttavia tutti i compilatori sembrano avere un modo di forzare l’impacchettamento dei byte di una struttura, come vedo già scoperto per GCC.

I bitfield devono essere evitati – non sono molto portabili tra i compilatori anche per la stessa piattaforma. dallo standard C99 6.7.2.1/10 – “Specifici struttura e unione” (c’è una formulazione simile nello standard C90):

Un’implementazione può allocare qualsiasi unità di memoria indirizzabile abbastanza grande da contenere un bitfield. Se rimane abbastanza spazio, un campo di bit che segue immediatamente un altro campo di bit in una struttura deve essere impacchettato in bit adiacenti della stessa unità. Se lo spazio insufficiente rimane, se un campo di bit che non si adatta viene inserito nell’unità successiva o si sovrappone alle unità adiacenti è definito dall’implementazione. L’ordine di assegnazione dei campi di bit all’interno di un’unità (ordine alto a basso ordine o basso ordine a alto ordine) è definito dall’implementazione. L’allineamento dell’unità di memoria indirizzabile non è specificato.

Non è ansible garantire se un campo di bit ‘span’ un limite int o no e non è ansible specificare se un bitfield inizia alla fine inferiore dell’int o dell’estremità superiore dell’int (ciò è indipendente dal fatto che il processore sia o meno big-endian o little-endian).

Preferisci maschere di bit. Usa inline (o anche macro) per impostare, cancellare e testare i bit.

endianness stanno parlando di ordini byte e non di ordini bit. Al giorno d’oggi , è sicuro al 99% che gli ordini di bit siano corretti. Tuttavia, quando si utilizzano bitfield, endianness dovrebbe essere preso in considerazione. Vedi l’esempio qui sotto.

 #include  typedef struct tagT{ int a:4; int b:4; int c:8; int d:16; }T; int main() { char data[]={0x12,0x34,0x56,0x78}; T *t = (T*)data; printf("a =0x%x\n" ,t->a); printf("b =0x%x\n" ,t->b); printf("c =0x%x\n" ,t->c); printf("d =0x%x\n" ,t->d); return 0; } //- big endian : mips24k-linux-gcc (GCC) 4.2.3 - big endian a =0x1 b =0x2 c =0x34 d =0x5678 // - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2 a =0x2 b =0x1 c =0x34 d =0x7856 

La maggior parte del tempo, probabilmente, ma non scommettere la fattoria su di esso, perché se ti sbagli, perderai grande.

Se davvero, hai davvero bisogno di avere informazioni binarie identiche, dovrai creare bitfield con le maschere di bit – ad esempio, usi un messaggio senza segno (16 bit) per Messaggio, e quindi crea qualcosa come versionMask = 0xE000 per rappresentare i tre bit più in alto.

C’è un problema simile con l’allineamento all’interno delle strutture. Ad esempio, le CPU Sparc, PowerPC e 680×0 sono tutte big-endian e l’impostazione predefinita comune per i compilatori Sparc e PowerPC è l’allineamento dei membri della struttura sui limiti a 4 byte. Tuttavia, un compilatore che ho usato per 680×0 era allineato solo sui confini a 2 byte e non c’era alcuna opzione per cambiare l’allineamento!

Quindi per alcune strutture, le dimensioni su Sparc e PowerPC sono identiche, ma più piccole su 680×0, e alcuni membri sono in offset di memoria diversi all’interno della struttura.

Questo era un problema con un progetto su cui ho lavorato, perché un processo del server in esecuzione su Sparc interrogava un client e scopriva che era big-endian, e supponeva che potesse solo schizzare le strutture binarie sulla rete e che il client potesse farcela. E ciò ha funzionato bene sui client PowerPC e si è bloccato in modo anomalo sui client 680×0. Non ho scritto il codice e ci è voluto un po ‘di tempo per trovare il problema. Ma è stato facile da risolvere una volta fatto.

Ovviamente la risposta migliore è usare una class che legge / scrive campi di bit come stream. L’uso della struttura di campo C bit non è garantito. Per non parlare di ciò è considerato poco professionale / pigro / stupido da usare nella codifica del mondo reale.