Perché questa dimensione della struttura è 3 invece di 2?


91

Ho definito questa struttura:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

Il sizeof(col)mi danno l'uscita di 3, ma non dovrebbe essere 2? Se commento solo un elemento, sizeofè 2. Non capisco perché: cinque elementi di 3 bit sono uguali a 15 bit, e questo è meno di 2 byte.

C'è una "dimensione interna" nel definire una struttura come questa? Ho solo bisogno di un chiarimento, perché dalla mia nozione di lingua fino ad ora, mi aspettavo una dimensione di 2 byte, non 3.


4
Probabilmente è l'ottimizzazione dell'allineamento. Inizia un nuovo byte, se la dimensione del bit successivo non si adatta allo spazio effettivamente occupato.
πάντα ῥεῖ

4
A meno che tu non abbia alcuni vincoli esterni che richiedono il pacchetto di bit e la tua piattaforma fornisce alcune garanzie aggiuntive rispetto a ciò che offre lo standard, non ha molto senso usare i bitfield.
David Rodríguez - dribeas

3
Si noti che per C, l'utilizzo di char è meno portabile rispetto all'utilizzo di int, stackoverflow.com/a/23987436/23118 .
hlovdal

2
Si noti che quasi tutto ciò che riguarda i campi di bit è definito dall'implementazione. Potresti ottenere risposte diverse da diversi compilatori e non ci sarebbe ricorso. Nota anche che poiché non hai specificato signed charo unsigned char, non puoi dire senza guardare la documentazione se il compilatore tratterà 'semplice' charin un campo bit come firmato o non firmato, e la decisione potrebbe (in teoria) essere diversa dalla decisione se 'plain' charè firmato o non firmato se usato al di fuori di un campo di bit.
Jonathan Leffler

3
Specificamente, in C99, §6.7.2.1 Struct e specificatori unione, ¶4 Un campo di bit deve avere un tipo che è una versione qualificato o non qualificato _Bool, signed int, unsigned int, o qualche altro tipo di implementazione definito. L'utilizzo charpertanto rientra nella categoria "altro tipo definito dall'implementazione".
Jonathan Leffler

Risposte:


95

Poiché stai utilizzando charcome tipo sottostante per i tuoi campi, il compilatore tenta di raggruppare bit per byte e poiché non può inserire più di otto bit in ogni byte, può memorizzare solo due campi per byte.

La somma totale dei bit utilizzati dalla tua struttura è 15, quindi la dimensione ideale per contenere così tanti dati sarebbe un file short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

Il codice sopra (per una piattaforma a 64 bit come la mia) produrrà effettivamente 2la seconda struttura. Per qualsiasi cosa più grande di a short, la struttura non riempirà più di un elemento del tipo utilizzato, quindi - per quella stessa piattaforma - la struttura avrà la dimensione quattro per int, otto per long, ecc.


1
La definizione della struttura proposta è ancora sbagliata. La corretta definizione della struttura userebbe 'unsigned short'.
user3629249

21
@ user3629249 Perché l'abbreviazione non firmata è "corretta"? Se l'utente desidera memorizzare da -4 a 3, short è corretto. Se l'utente desidera memorizzare da 0 a 7, il corto senza segno è corretto. La domanda originale utilizzava un carattere firmato, ma non posso dire se fosse intenzionale o accidentale.
Bruce Dawson

2
Perché c'è la differenza tra chare short?
GingerPlusPlus

5
@BruceDawson: Lo standard consente che le implementazioni charnon siano firmate ...
Thomas Eding,

@ThomasEding True, lo standard consente a char di non essere firmato. Ma il punto principale rimane, che non è stata fornita alcuna ragione per affermare che il corto non firmato fosse corretto (anche se di solito lo sarà).
Bruce Dawson

78

Perché non puoi avere un campo di pacchetti di bit che si estende attraverso il limite di allineamento minimo (che è 1 byte), quindi probabilmente verranno impacchettati come

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(gli ordini di campo / riempimento all'interno dello stesso byte non sono intenzionali, è solo per darti l'idea, dal momento che il compilatore potrebbe impostarli come preferisce)


16

I primi due campi di bit rientrano in un unico char. Il terzo non può adattarsi a questo chare ha bisogno di uno nuovo. 3 + 3 + 3 = 9 che non rientra in un carattere a 8 bit.

Quindi la prima coppia prende un char, la seconda coppia prende un chare il campo dell'ultimo bit ottiene un terzo char.


15

La maggior parte dei compilatori ti permette di controllare il riempimento, ad esempio usando #pragmas . Ecco un esempio con GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Nota che il comportamento predefinito del compilatore esiste per una ragione e probabilmente ti darà prestazioni migliori.


9

Anche se lo standard ANSI C specifica troppo poco su come i bitfield sono impacchettati per offrire un vantaggio significativo rispetto a "i compilatori possono impacchettare i bitfield come ritengono opportuno", tuttavia in molti casi proibisce ai compilatori di impacchettare le cose nel modo più efficiente.

In particolare, se una struttura contiene campi di bit, un compilatore è tenuto a memorizzarla come una struttura che contiene uno o più campi anonimi di un tipo di memorizzazione "normale" e quindi suddivide logicamente ciascuno di tali campi nelle sue parti di campo di bit costituenti. Quindi, dato:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Se unsigned charè di 8 bit, il compilatore dovrebbe allocare quattro campi di quel tipo e assegnare due campi di bit a tutti tranne uno (che sarebbe in un charcampo a sé stante). Se tutte le chardichiarazioni fossero state sostituite con short, ci sarebbero due campi di tipo short, uno dei quali conterrebbe cinque campi di bit e l'altro conterrebbe i due rimanenti.

Su un processore senza limitazioni di allineamento, i dati potrebbero essere disposti in modo più efficiente utilizzando unsigned shortper i primi cinque campi e unsigned charper gli ultimi due, memorizzando sette campi a tre bit in tre byte. Sebbene dovrebbe essere possibile memorizzare otto campi a tre bit in tre byte, un compilatore potrebbe consentirlo solo se esistesse un tipo numerico a tre byte che potrebbe essere utilizzato come tipo "campo esterno".

Personalmente, considero i bitfield definiti come fondamentalmente inutili. Se il codice deve lavorare con dati binari, dovrebbe definire esplicitamente le posizioni di archiviazione dei tipi effettivi e quindi utilizzare le macro o altri mezzi simili per accedere ai relativi bit. Sarebbe utile se C supportasse una sintassi come:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Una tale sintassi, se consentita, consentirebbe al codice di utilizzare i campi di bit in modo portabile, senza riguardo per le dimensioni delle parole o l'ordinamento dei byte (foo0 sarebbe nei tre bit meno significativi di f1, ma questi potrebbero essere memorizzati nel indirizzo inferiore o superiore). In assenza di tale caratteristica, tuttavia, le macro sono probabilmente l'unico modo portatile per operare con queste cose.


2
Diversi compilatori disporranno i campi di bit in modo diverso. Ho scritto della documentazione su come fa Visual C ++ che potrebbe essere rilevante. Indica alcune delle fastidiose insidie: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson

Bene, stai dicendo un equivalente di memorizzare in un tipo normale e utilizzare l'operatore di campo di bit per realizzare la singola variabile di interesse e per semplificare questo meccanismo utilizzare alcune macro. Penso che anche il codice generato in c / c ++ faccia qualcosa di simile. Usare una struttura serve solo per una "migliore" organizzazione del codice, anzi non è affatto necessario.
Raffaello
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.