Come leggere / scrivere bit arbitrari in C / C ++

Supponendo che ho un byte b con il valore binario di 11111111

Come faccio ad esempio a leggere un valore intero a 3 bit a partire dal secondo bit o scrivere un valore intero a quattro bit a partire dal quinto bit?

Circa 2 anni dopo aver posto questa domanda mi piacerebbe spiegarlo nel modo in cui vorrei che fosse spiegato quando ero ancora un newb completo e sarebbe stato di grande beneficio per le persone che vogliono capire il processo.

Prima di tutto, dimentica il valore di esempio “11111111”, che non è proprio tutto ciò che è adatto per la spiegazione visiva del processo. Quindi il valore iniziale è 10111011 (187 decimale), che sarà un po ‘più illustrativo del processo.

1 – come leggere un valore a 3 bit a partire dal secondo bit:

  ___ < - those 3 bits 10111011 

Il valore è 101 o 5 in decimale, ci sono 2 possibili modi per ottenerlo:

  • maschera e spostamento

In questo approccio, i bit necessari vengono prima mascherati con il valore 00001110 (14 decimali) dopo il quale viene spostato sul posto:

  ___ 10111011 AND 00001110 = 00001010 >> 1 = ___ 00000101 

L'espressione per questo sarebbe: (value & 14) >> 1

  • spostamento e maschera

Questo approccio è simile, ma l'ordine delle operazioni è invertito, il che significa che il valore originale viene spostato e mascherato con 00000111 (7) per lasciare solo gli ultimi 3 bit:

  ___ 10111011 >> 1 ___ 01011101 AND 00000111 00000101 

L'espressione per questo sarebbe: (value >> 1) & 7

Entrambi gli approcci implicano la stessa quantità di complessità e pertanto non differiscono in termini di prestazioni.

2 - come scrivere un valore a 3 bit a partire dal secondo bit:

In questo caso, il valore iniziale è noto e, quando questo è il caso nel codice, potresti essere in grado di trovare un modo per impostare il valore noto su un altro valore noto che utilizza meno operazioni, ma in realtà questo è raramente il valore caso, la maggior parte delle volte il codice non conoscerà né il valore iniziale, né quello che deve essere scritto.

Ciò significa che affinché il nuovo valore venga "splicato" correttamente in byte, i bit di destinazione devono essere impostati su zero, dopo di che il valore spostato è "splicato" sul posto, che è il primo passo:

  ___ 10111011 AND 11110001 (241) = 10110001 (masked original value) 

Il secondo passo è quello di spostare il valore che vogliamo scrivere nei 3 bit, diciamo che vogliamo cambiare quello da 101 (5) a 110 (6)

  ___ 00000110 < < 1 = ___ 00001100 (shifted "splice" value) 

Il terzo e ultimo passo è quello di unire il valore originale mascherato con il valore "splice" spostato:

 10110001 OR 00001100 = ___ 10111101 

L'espressione per l'intero processo sarebbe: (value & 241) | (6 < < 1) (value & 241) | (6 < < 1)

Bonus: come generare le maschere di lettura e scrittura:

Ovviamente, l'uso di un convertitore da binario a decimale è tutt'altro che elegante, specialmente nel caso di contenitori a 32 e 64 bit - i valori decimali diventano enormi. È ansible generare facilmente maschere con espressioni, che il compilatore può risolvere in modo efficiente durante la compilazione:

  • read mask for "mask and shift": ((1 < < fieldLength) - 1) << (fieldIndex - 1) , assumendo che l'indice al primo bit sia 1 (non zero)
  • leggi maschera per "shift e maschera": (1 < < fieldLength) - 1 (l'indice non ha un ruolo qui poiché è sempre spostato sul primo bit
  • maschera di scrittura: basta invertire l'espressione maschera "maschera e sposta" con l'operatore ~

Come funziona (con il campo a 3 bit che inizia al secondo bit degli esempi sopra)?

 00000001 < < 3 00001000 - 1 00000111 << 1 00001110 ~ (read mask) 11110001 (write mask) 

Gli stessi esempi si applicano agli interi più ampi e alla larghezza e alla posizione dei campi arbitrari, con i valori di spostamento e maschera che variano di conseguenza.

Si noti inoltre che gli esempi assumono numero intero senza segno, che è quello che si desidera utilizzare per utilizzare gli interi come alternativa di campo bit portatile (i campi di bit regolari non sono in alcun modo garantiti dallo standard per essere portatili), sia lo spostamento a sinistra che a destra inserisci un padding 0, che non è il caso con lo spostamento a destra di un intero con segno.

Ancora più semplice:

Utilizzando questo insieme di macro (ma solo in C ++ poiché si basa sulla generazione di funzioni membro):

 #define GETMASK(index, size) (((1 < < (size)) - 1) << (index)) #define READFROM(data, index, size) (((data) & GETMASK((index), (size))) >> (index)) #define WRITETO(data, index, size, value) ((data) = ((data) & (~GETMASK((index), (size)))) | ((value) < < (index))) #define FIELD(data, name, index, size) \ inline decltype(data) name() { return READFROM(data, index, size); } \ inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); } 

Si potrebbe andare per qualcosa di semplice come:

 struct A { uint bitData; FIELD(bitData, one, 0, 1) FIELD(bitData, two, 1, 2) }; 

E avere i campi di bit implementati come proprietà a cui è ansible accedere facilmente:

 A a; a.set_two(3); cout < < a.two(); 

Sostituisci decltype con gcc's typeof pre-C ++ 11.

Devi spostare e mascherare il valore, quindi per esempio …

Se vuoi leggere i primi due bit, devi solo mascherarli in questo modo:

 int value = input & 0x3; 

Se vuoi compensarlo devi spostare a destra N bit e quindi mascherare i bit che vuoi:

 int value = (intput >> 1) & 0x3; 

Per leggere tre bit come hai chiesto nella tua domanda.

 int value = (input >> 1) & 0x7; 

Devi fare un’operazione di spostamento e maschera (AND). Sia b un qualunque byte e p sia l’indice (> = 0) del bit da cui vuoi prendere n bit (> = 1).

Per prima cosa devi spostare a destra b per p volte:

 x = b >> p; 

Secondo, devi mascherare il risultato con n uno:

 mask = (1 < < n) - 1; y = x & mask; 

Puoi mettere tutto in una macro:

 #define TAKE_N_BITS_FROM(b, p, n) ((b) >> (p)) & ((1 < < (n)) - 1) 

“Come faccio ad esempio a leggere un valore intero a 3 bit a partire dal secondo bit?”

 int number = // whatever; uint8_t val; // uint8_t is the smallest data type capable of holding 3 bits val = (number & (1 < < 2 | 1 << 3 | 1 << 4)) >> 2; 

(Supponevo che “second bit” fosse il bit n. 2, ovvero il terzo bit in realtà.)

basta usare questo e feelfree:

 #define BitVal(data,y) ( (data>>y) & 1) /** Return Data.Y value **/ #define SetBit(data,y) data |= (1 < < y) /** Set Data.Y to 1 **/ #define ClearBit(data,y) data &= ~(1 << y) /** Clear Data.Y to 0 **/ #define TogleBit(data,y) (data ^=BitVal(y)) /** Togle Data.Y value **/ #define Togle(data) (data =~data ) /** Togle Data value **/ 

per esempio:

 uint8_t number = 0x05; //0b00000101 uint8_t bit_2 = BitVal(number,2); // bit_2 = 1 uint8_t bit_1 = BitVal(number,1); // bit_1 = 0 SetBit(number,1); // number = 0x07 => 0b00000111 ClearBit(number,2); // number =0x03 => 0b0000011 

Per leggere i byte usa std :: bitset

 const int bits_in_byte = 8; char myChar = 's'; cout < < bitset(myChar); 

Per scrivere devi usare operatori bit-like come & ^ | & < < >>. assicurati di sapere cosa fanno.

Ad esempio per avere 00100100 è necessario impostare il primo bit su 1 e spostarlo con gli operatori < < >> per 5 volte. se vuoi continuare a scrivere, continua a impostare il primo bit e a spostarlo. è molto simile a una vecchia macchina da scrivere: scrivi e sposta la carta.

Per 00100100: imposta il primo bit su 1, sposta 5 volte, imposta il primo bit su 1 e sposta 2 volte:

 const int bits_in_byte = 8; char myChar = 0; myChar = myChar | (0x1 < < 5 | 0x1 << 2); cout << bitset(myChar); 
 int x = 0xFF; //your number - 11111111 

Come faccio ad esempio leggere un valore intero a 3 bit a partire dal secondo bit

 int y = x & ( 0x7 < < 2 ) // 0x7 is 111 // and you shift it 2 to the left 

Se continui ad afferrare bit dai tuoi dati, potresti voler utilizzare un bitfield. Dovrai solo configurare una struttura e caricarla con solo uno e zero:

 struct bitfield{ unsigned int bit : 1 } struct bitfield *bitstream; 

poi in seguito caricalo in questo modo (sostituendo char con int o con qualsiasi dato che stai caricando):

 long int i; int j, k; unsigned char c, d; bitstream=malloc(sizeof(struct bitfield)*charstreamlength*sizeof(char)); for (i=0; i>(sizeof(char)*8-j-1); d=d< <(sizeof(char)*8-1); k=d; if(k==0){ bitstream[sizeof(char)*8*i + j].bit=0; }else{ bitstream[sizeof(char)*8*i + j].bit=1; } } } 

Quindi accedi agli elementi:

 bitstream[bitpointer].bit=... 

o

 ...=bitstream[bitpointer].bit 

Tutto ciò presuppone che stiano lavorando su i86 / 64, non su arm, poiché il arm può essere big o piccolo endian.