2013-02-28 9 views
7

ho questa struct C: (in rappresentanza di un datagramma IP)Memoria di campi di bit struct avere

struct ip_dgram 
{ 
    unsigned int ver : 4; 
    unsigned int hlen : 4; 
    unsigned int stype : 8; 
    unsigned int tlen : 16; 
    unsigned int fid : 16; 
    unsigned int flags : 3; 
    unsigned int foff : 13; 
    unsigned int ttl : 8; 
    unsigned int pcol : 8; 
    unsigned int chksm : 16; 
    unsigned int src : 32; 
    unsigned int des : 32; 
    unsigned char opt[40]; 
}; 

sto assegnazione di valori ad esso, e quindi la stampa il suo layout di memoria in parole di 16 bit come questo:

//prints 16 bits at a time 
void print_dgram(struct ip_dgram dgram) 
{ 
    unsigned short int* ptr = (unsigned short int*)&dgram; 
    int i,j; 
    //print only 10 words 
    for(i=0 ; i<10 ; i++) 
    { 
     for(j=15 ; j>=0 ; j--) 
     { 
      if((*ptr) & (1<<j)) printf("1"); 
      else printf("0"); 
      if(j%8==0)printf(" "); 
     } 
     ptr++; 
     printf("\n"); 
    } 
} 

int main() 
{ 
    struct ip_dgram dgram; 

    dgram.ver = 4; 
    dgram.hlen = 5; 
    dgram.stype = 0; 
    dgram.tlen = 28; 
    dgram.fid = 1; 
    dgram.flags = 0; 
    dgram.foff = 0; 
    dgram.ttl = 4; 
    dgram.pcol = 17; 
    dgram.chksm = 0; 
    dgram.src = (unsigned int)htonl(inet_addr("10.12.14.5")); 
    dgram.des = (unsigned int)htonl(inet_addr("12.6.7.9")); 

    print_dgram(dgram); 

    return 0; 
} 

ottengo questo output:

00000000 01010100 
00000000 00011100 
00000000 00000001 
00000000 00000000 
00010001 00000100 
00000000 00000000 
00001110 00000101 
00001010 00001100 
00000111 00001001 
00001100 00000110 

Ma mi aspetto che questo:

012.351.641.061.

enter image description here

L'uscita è parzialmente corretta; da qualche parte, i byte e i bocconcini sembrano essere scambiati. C'è qualche problema di endianità qui? I bit-field non sono adatti a questo scopo? Davvero non lo so. Qualsiasi aiuto? Grazie in anticipo!

+0

possibile duplicato di [Perché bit endianness è un problema in bitfield?] (Http://stackoverflow.com/questions/6043483/why-bit-endianness-is-an-issue-in-bitfields) – Lundin

+0

Dai uno sguardo a come ad es lo stack di rete Linux o BSD fa questo. – vonbrand

risposta

7

No, i bitfield non sono adatti a questo scopo. Il layout dipende dal compilatore.

Generalmente non è consigliabile utilizzare bitfield per i dati in cui si desidera controllare il layout risultante, a meno che non si disponga di (specifico del compilatore), ad esempio #pragma s, per farlo.

Il modo migliore è probabilmente quello di implementare questo senza bitfield, cioè eseguendo le operazioni bit a bit necessarie da soli. Questo è fastidioso, ma molto più semplice di un modo per risolvere questo problema. Inoltre, è indipendente dalla piattaforma.

Definire l'intestazione come un array di parole a 16 bit e quindi è possibile calcolare il checksum in modo sufficientemente semplice.

+0

Quindi cosa posso fare per controllare il layout? Ho bisogno di loro contigui, dal momento che devo calcolare il loro checksum, che è la somma del complemento 1 di tutte le parole di 16 bit nel datagramma. Se faccio tutti i campi come interi senza segno a 32 bit, non riesco a vedere come troverò la loro somma .. – Bruce

+0

'__attribute __ ((impacchettato))' su gcc e vuoto campo zero ': 0' per forzare l'allineamento a la prossima parola. –

+0

@tristopia Puoi spiegare più chiaramente? O renderlo una risposta? – Bruce

-1

Sono d'accordo con quanto detto. I campi bit dipendono dal compilatore.

Se i bit devono essere in un ordine specifico, inserire i dati in un puntatore a un array di caratteri. Incrementa il buffer della dimensione dell'elemento che viene compresso. Prepara il prossimo elemento.

pack(char** buffer) 
{ 
    if (buffer & *buffer) 
    { 
    //pack ver 
    //assign first 4 bits to 4. 
    *((UInt4*) *buffer) = 4; 
    *buffer += sizeof(UInt4); 

    //assign next 4 bits to 5 
    *((UInt4*) *buffer) = 5; 
    *buffer += sizeof(UInt4); 

    ... continue packing 
    } 
} 
+0

Stai cercando di suggerire che 'sizeof (UInt4)' potrebbe essere da qualche parte tra 0 e 1, come 0.5, perché un UInt4 è "metà di un byte"? Roffle. Vorrei poterti votare per questo! – Sebivor

+0

Supponendo 'CHAR_BIT == 9', ciò implica che il * valore intero *' sizeof (UInt4) 'è una * frazione irrazionale *? – Sebivor

+0

I singoli bit non sono indirizzabili. L'entità minima indirizzabile in C è char (aka byte), che di solito ha 8 bit. – WGH

0

Lo standard C11 dice:

L'attuazione può assegnare qualsiasi unità di memorizzazione indirizzabile grande abbastanza per 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 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 di ordine basso a ordine alto) è definito dall'implementazione.

Sono abbastanza sicuro che questo non è consigliabile, poiché significa che potrebbe esserci un padding tra i campi e che non è possibile controllare l'ordine dei campi. Non solo, ma sei al capriccio dell'implementazione in termini di ordine di byte di rete.Inoltre, immaginate se un unsigned int è solo 16 bit, e si sta chiedendo per adattarsi a un campo di bit a 32-bit in esso:

L'espressione che specifica la larghezza di un campo di bit deve essere un'espressione costante intera con un valore non negativo che non supera la larghezza di un oggetto del tipo che sarebbe stato specificato erano i due punti e l'espressione omessi.

Suggerisco di utilizzare un array di unsigned char s invece di una struttura. In questo modo è garantito il controllo su padding e ordine di byte di rete. Inizia con la dimensione in bit che vuoi che la tua struttura sia, in totale. Darò per scontato che stai dichiarando questo in una costante come IP_PACKET_BITCOUNT: typedef unsigned char ip_packet[(IP_PACKET_BITCOUNT/CHAR_BIT) + (IP_PACKET_BITCOUNT % CHAR_BIT > 0)];

Scrivete una funzione, void set_bits(ip_packet p, size_t bitfield_offset, size_t bitfield_width, unsigned char *value) { ... } che consente di impostare i bit a partire da p[bitfield_offset/CHAR_BIT] po bitfield_offset % CHARBIT ai bit si trovano in valore, fino a bitfield_width bit in lunghezza. Questa sarà la parte più complicata del tuo compito.

Quindi è possibile definire identificatori per VER_OFFSET 0 e VER_WIDTH 4, HLEN_OFFSET 4 e HLEN_WIDTH 4, ecc. Per rendere la modifica dell'array meno semplice.

+0

Sono un po 'perplesso sul motivo per cui i bitfield vengono specificati come sono nello standard. Se esistessero mezzi portatili per disporre esplicitamente i bitfield, ciò potrebbe essere molto utile; poiché non c'è, non sono sicuro di cosa si ottiene ad es. specificare le regole in un modo che impedisca in modo efficace a un compilatore a 32 bit di consentire ad es. due numeri a dodici bit per adattarsi a una struttura a tre byte. Riesco a capire che non voglio * richiedere * ai compilatori di eseguire un imballaggio così preciso, ma la mia comprensione è che al compilatore non sarebbe consentito che i campi si sovrappongano a entrambi i limiti dei byte. – supercat

+0

@supercat Sì, i bitfield sono una caratteristica piuttosto inutile e noiosa, a differenza dei magici puntatori di "UInt4" in [risposta di Kat] (http://stackoverflow.com/a/15139924/1989425) che sembrano essere in grado di puntare a * mezzo byte *. Sarebbe bello poter dichiarare array di loro, ad es. una serie di bitfield 'sizeof (int) * CHAR_BIT' di dimensione 1, per manipolare' bitfield [0] 'attraverso' bitfield [sizeof (int) * CHAR_BIT - 1] '. – Sebivor

+0

Riesci a pensare a qualcosa di pratico che un programma pratico esistente o prospettico ben definito possa fare con i campi bit definiti come sono quelli che un programma ben definito non sarebbe in grado di fare se le implementazioni fossero libere di organizzare o tamponare i bitfield in qualsiasi soggetto di moda conveniente solo per il vincolo che un campo bit non dovrebbe essere più grande di una struttura con gli stessi tipi e che un "elemento" a zero bit o un oggetto non bitfield imporrà l'allineamento appropriato per il tipo dichiarato? – supercat

0

Compilatore dipendente o no, dipende se si desidera scrivere un programma molto veloce o se si desidera uno che funziona con diversi compilatori. Per scrivere per C un'applicazione veloce e compatta, utilizzare uno stuct con campi di bit /. Se vuoi un programma per scopi generali lenti, codificalo a lungo.

+0

Ciao, questa non è una risposta molto specifica e non si esegue il backup dei propri argomenti con nulla. Puoi aggiungere qualche esempio/prova in? – Ben

1

Sebbene la domanda sia stata posta molto tempo fa, non c'è risposta con la spiegazione del risultato. Risponderò, spero che possa essere utile a qualcuno.

Illustrerò il bug utilizzando i primi 16 bit della struttura dati.

Nota: questa spiegazione è ritenuta valida solo per il set del processore e del compilatore. Se qualcuno di questi cambiamenti, il comportamento potrebbe cambiare.

campi:

unsigned int ver : 4; 
unsigned int hlen : 4; 
unsigned int stype : 8; 

Assegnato a:

dgram.ver = 4; 
dgram.hlen = 5; 
dgram.stype = 0; 

compilatore inizia l'assegnazione di campi di bit a partire all'offset 0. Questo significa primo byte della struttura dei dati è memorizzato come:

Bit offset: 7  4  0 
      ------------- 
      | 5 | 4 | 
      ------------- 

primi 16 bit dopo aspetto incarico come questo:

Bit offset:  15 12 8  4  0 
       ------------------------- 
       | 5 | 4 | 0 | 0 | 
       ------------------------- 
Memory Address: 100   101 

Si utilizza Unsigned 16 puntatore a indirizzo di memoria dereference 100. Di conseguenza l'indirizzo 100 viene trattato come LSB di un numero a 16 bit. E 101 viene trattato come MSB di un numero a 16 bit.

Se si stampa * PTR in esadecimale vedrete questo:

*ptr = 0x0054 

il ciclo è in esecuzione su questo valore a 16 bit e, quindi, si ottiene:

00000000 0101 0100 
-------- ---- ---- 
    0  5 4 

Soluzione: Modifica dell'ordine degli elementi a

unsigned int hlen : 4; 
unsigned int ver : 4; 
unsigned int stype : 8; 

E utilizzare il puntatore char * non firmato per attraversare e stampare valori. Dovrebbe funzionare.

Si noti che, come altri hanno detto, questo comportamento è specifico della piattaforma e del compilatore. Se una di queste modifiche è necessaria, è necessario verificare che il layout della memoria della struttura dati sia corretto.