C / C ++: Force Bit Field Order and Alignment


87

Ho letto che l'ordine dei campi di bit all'interno di una struttura è specifico della piattaforma. E se utilizzo diverse opzioni di imballaggio specifiche del compilatore, i dati di garanzia verranno archiviati nell'ordine corretto così come 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 sono stati disposti in memoria così come vengono visualizzati. Message.versionerano i primi 3 bit nel buffer e Message.typeseguiti. Se trovo opzioni di impacchettamento della struttura equivalenti per vari compilatori, sarà multipiattaforma?


17
Poiché un buffer è un insieme di byte, non di bit, "i primi 3 bit nel buffer" non è un concetto preciso. Considereresti che i 3 bit di ordine più basso del primo byte fossero i primi 3 bit o i 3 bit di ordine più alto?
caffè

2
Durante il transito sulla rete, "I primi 3 bit nel buffer" risulta essere molto ben definito.
Joshua

2
@Joshua IIRC, Ethernet trasmette il bit meno significativo di ciascun byte prima (per questo motivo il bit di trasmissione è dove è).
tc.

Quando dici "portatile" e "multipiattaforma", cosa intendi? L'eseguibile accederà correttamente all'ordine indipendentemente dal sistema operativo di destinazione, oppure il codice verrà compilato indipendentemente dalla toolchain?
Garet Claborn

Risposte:


103

No, non sarà completamente portatile. Le opzioni di impacchettamento per le strutture sono estensioni e esse stesse non sono completamente portabili. 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à (da alto a basso o da basso a alto) è definito dall'implementazione".

Anche un singolo compilatore potrebbe disporre il campo di bit in modo diverso a seconda dell'endianness della piattaforma di destinazione, ad esempio.


Sì, il GCC, ad esempio, osserva specificamente che i campi di bit sono disposti secondo l'ABI, non l'implementazione. Quindi, rimanere su un singolo compilatore non è sufficiente per garantire l'ordine. Anche l'architettura deve essere controllata. Un po 'un incubo per la portabilità, davvero.
underscore_d

10
Perché lo standard C non garantisce un ordine per i campi di bit?
Aaron Campbell

8
È difficile definire in modo coerente e portabile "l'ordine" dei bit all'interno dei byte, tanto meno l'ordine dei bit che possono attraversare i confini dei byte. Qualsiasi definizione su cui ti stabilisci non riuscirà a corrispondere a una quantità considerevole di pratica esistente.
Stephen Canon

2
definito dall'implementazione consente l'ottimizzazione specifica della piattaforma. Su alcune piattaforme, il riempimento tra i campi di bit può migliorare l'accesso, immagina quattro campi a sette bit in un int a 32 bit: allinearli ad ogni 8 bit è un miglioramento significativo per le piattaforme che hanno letture di byte.
Peterchen


45

I campi di bit variano ampiamente da compilatore a compilatore, mi dispiace.

Con GCC, le macchine big endian dispongono prima i bit big end e le macchine little endian dispongono prima i bit small end.

K&R afferma: "I membri adiacenti dei campi [bit-] delle strutture sono imballati in unità di memorizzazione dipendenti dall'implementazione in una direzione dipendente dall'implementazione. Quando un campo che segue un altro campo non si adatta ... potrebbe essere diviso tra imbottito. 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 campi non bit a causa del riempimento, tuttavia tutti i compilatori sembrano avere un modo per forzare il confezionamento di byte di una struttura, come vedo che hai già scoperto per GCC.


K&R è davvero considerato un riferimento utile, dato che era pre-standardizzazione e probabilmente è stato (presumo?) Sostituito in molte aree?
underscore_d

1
Il mio K&R è post-ANSI.
Joshua

1
Questo è imbarazzante: non mi ero reso conto che avessero rilasciato una revisione post-ANSI. Colpa mia!
underscore_d

35

I bitfield dovrebbero essere evitati: non sono molto portabili tra i compilatori anche per la stessa piattaforma. dallo standard C99 6.7.2.1/10 - "Specificatori di struttura e unione" (c'è una formulazione simile nello standard C90):

Un'implementazione può allocare qualsiasi unità di memoria indirizzabile abbastanza grande da contenere un campo di bit. 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 rimane spazio insufficiente, l'implementazione definisce se un campo di bit che non si adatta viene inserito nell'unità successiva o si sovrappone a unità adiacenti. L'ordine di allocazione dei campi di bit all'interno di un'unità (da ordine alto a ordine basso o da ordine basso a ordine alto) è definito dall'implementazione. L'allineamento dell'unità di memoria indirizzabile non è specificato.

Non puoi garantire se un campo di bit 'estenderà' o meno un limite int e non puoi specificare se un campo di bit inizia all'estremità inferiore di int o all'estremità alta di int (questo èindipendente dal fatto che il processore sia big-endian o little-endian).

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


2
L'ordine dei campi di bit può essere determinato in fase di compilazione.
Greg A. Woods

9
Inoltre, i campi di bit sono altamente preferiti quando si ha a che fare con flag di bit che non hanno alcuna rappresentazione esterna al di fuori del programma (cioè su disco o in registri o in memoria a cui si accede da altri programmi, ecc.).
Greg A. Woods

1
@ GregA.Woods: Se è davvero così, fornisci una risposta descrivendo come. Non sono riuscito a trovare nient'altro che il tuo commento quando ho
cercato su Google

1
@ GregA.Woods: Scusa, avrei dovuto scrivere a quale commento mi riferivo. Volevo dire: dici che "L'ordine dei campi di bit può essere determinato in fase di compilazione.". Non posso niente a riguardo e come farlo.
mozzbozz

2
@mozzbozz Dai un'occhiata a planix.com/~woods/projects/wsg2000.c e cerca le definizioni e l'uso di _BIT_FIELDS_LTOHe_BIT_FIELDS_HTOL
Greg A. Woods

11

endianness parla di ordini di byte, non di ordini di bit. Al giorno d'oggi , è sicuro al 99% che gli ordini di bit siano corretti. Tuttavia, quando si usano i bitfield, è necessario tenere conto dell'endianness. Vedi l'esempio sotto.

#include <stdio.h>

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
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

6
L'output di aeb indica che endianness sta ancora parlando di ordini di bit E ordini di byte.
Programmatore Windows,

meraviglioso esempio con problemi di ordinamento dei bit e ordinamento dei byte
Jonathan

1
Hai effettivamente compilato ed eseguito il codice? I valori per "a" e "b" non mi sembrano logici: in pratica stai dicendo che il compilatore scambierà i nibble all'interno di un byte a causa dell'endianness. Nel caso di "d", endiannes non dovrebbe influenzare l'ordine dei byte all'interno degli array di caratteri (assumendo che char sia lungo 1 byte); se il compilatore lo facesse, non saremmo in grado di iterare attraverso un array usando i puntatori. Se invece avessi usato un array di due interi a 16 bit es: uint16 data [] = {0x1234,0x5678}; allora d sarebbe sicuramente 0x7856 nei sistemi little endian.
Krauss

6

La maggior parte delle volte, probabilmente, ma non scommetterci sopra, perché se sbagli, perderai alla grande.

Se hai davvero, davvero bisogno di avere informazioni binarie identiche, dovrai creare campi di bit con maschere di bit, ad esempio usi un corto senza segno (16 bit) per Message, e poi fai cose come versionMask = 0xE000 per rappresentare i tre bit più in alto.

C'è un problema simile con l'allineamento all'interno degli struct. Ad esempio, le CPU Sparc, PowerPC e 680x0 sono tutte big-endian e l'impostazione predefinita comune per i compilatori Sparc e PowerPC è allineare i membri della struttura su limiti di 4 byte. Tuttavia, un compilatore che ho usato per 680x0 era allineato solo su limiti di 2 byte e non c'era alcuna opzione per modificare l'allineamento!

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

Questo era un problema con un progetto su cui ho lavorato, perché un processo server in esecuzione su Sparc avrebbe interrogato un client e avrebbe scoperto che era big-endian, e presumendo che potesse semplicemente spruzzare strutture binarie sulla rete e il client poteva farcela. E questo ha funzionato bene sui client PowerPC e si è arrestato in modo anomalo sui client 680x0. Non ho scritto il codice e ci è voluto un po 'di tempo per trovare il problema. Ma è stato facile risolverlo una volta che l'ho fatto.


1

Grazie @BenVoigt per il tuo utilissimo commento iniziale

No, sono stati creati per risparmiare memoria.

Source Linux fa utilizzare un campo di bit per abbinare ad una struttura esterna: /usr/include/linux/ip.h ha questo codice per il primo byte di un datagramma IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Tuttavia alla luce del tuo commento sto rinunciando a provare a farlo funzionare per il campo bit multibyte frag_off .


-9

Ovviamente la risposta migliore è usare una classe che legge / scrive campi di bit come flusso. L'uso della struttura del campo del bit C non è garantito. Per non parlare del fatto che è considerato poco professionale / pigro / stupido usarlo nella codifica del mondo reale.


5
Penso che sia sbagliato affermare che è stupido usare i campi di bit poiché fornisce un modo molto pulito per rappresentare i registri hardware, che è stato creato per modellare, in C.
trondd

13
@trondd: No, sono stati creati per risparmiare memoria. I campi di bit non sono destinati a mappare a strutture di dati esterne, come registri hardware mappati in memoria, protocolli di rete o formati di file. Se fossero destinati a mappare a strutture dati esterne, l'ordine di imballaggio sarebbe stato standardizzato.
Ben Voigt

2
L'uso dei bit consente di risparmiare memoria. L'uso di campi di bit aumenta la leggibilità. Usare meno memoria è più veloce. L'uso dei bit consente operazioni atomiche più complesse. Nelle nostre applicazioni nel mondo reale, sono necessarie prestazioni e operazioni atomiche complesse. Questa risposta non funzionerebbe per noi.
johnnycrash

@BenVoigt probabilmente è vero, ma se un programmatore è disposto a confermare che l'ordinamento del proprio compilatore / ABI corrisponde a ciò di cui ha bisogno e di conseguenza sacrifica la portabilità rapida, allora può certamente svolgere quel ruolo. Per quanto riguarda 9 *, quale massa autorevole di "programmatori del mondo reale" considera tutto l'uso di bitfield "non professionale / pigro / stupido" e dove lo hanno affermato?
underscore_d

2
Usare meno memoria non è sempre più veloce; spesso è più efficiente utilizzare più memoria e ridurre le operazioni di post-lettura, e la modalità processore / processore può renderlo ancora più vero.
Dave Newton,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.