Подтвердить что ты не робот

Как читать/писать произвольные биты в C/С++

Предполагая, что у меня есть байт b с двоичным значением 11111111

Как я, например, читаю 3-битное целочисленное значение, начиная со второго бита, или записываю четырехзначное целочисленное значение, начиная с пятого бита?

4b9b3361

Ответ 1

Примерно через 2 года после того, как я задал этот вопрос, я хотел бы объяснить это так, как я бы хотел, чтобы он объяснил это, когда я все еще был полным новичком, и был бы наиболее полезным для людей, которые хотят понять этот процесс.

Прежде всего, забудьте значение "11111111", которое на самом деле не все, что подходит для визуального объяснения процесса. Поэтому пусть начальное значение будет 10111011 (187 десятичных), что будет немного более иллюстрацией процесса.

1 - как читать 3-битное значение, начиная со второго бита:

    ___  <- those 3 bits
10111011 

Значение равно 101 или 5 в десятичной форме, есть два возможных способа его получения:

  • маска и смена

В этом подходе необходимые бит сначала маскируются значением 00001110 (14 десятичных знаков), после чего он сдвигается на месте:

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

Выражение для этого было бы: (value & 14) >> 1

  • сдвиг и маска

Этот подход аналогичен, но порядок операций меняется на противоположное, что означает, что исходное значение смещается, а затем замаскировано с помощью 00000111 (7), чтобы оставить только последние 3 бит:

    ___
10111011 >> 1
     ___
01011101 AND
00000111
00000101

Выражение для этого было бы: (value >> 1) & 7

Оба подхода включают в себя такую ​​же сложность и, следовательно, не будут отличаться по производительности.

2 - как записать 3-битное значение, начиная со второго бита:

В этом случае начальное значение известно, и, когда это имеет место в коде, вы можете придумать способ установить известное значение в другое известное значение, которое использует меньше операций, но на самом деле это редко бывает, в большинстве случаев код не будет знать ни начального значения, ни того, которое должно быть записано.

Это означает, что для того, чтобы новое значение было успешно "сплайсировано" в байте, целевые биты должны быть установлены на ноль, после чего сдвинутое значение "сплайсировано" на месте, что является первым шагом:

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

Второй шаг - сдвинуть значение, которое мы хотим записать в 3 битах, скажем, мы хотим изменить это с 101 (5) до 110 (6)

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

Третий и последний шаг - объединить замаскированное исходное значение со сдвинутым значением "сращивания":

10110001 OR
00001100 =
    ___
10111101

Выражение для всего процесса: (value & 241) | (6 << 1)

Бонус - как создать маски чтения и записи:

Естественно, использование двоично-десятичного конвертера далеко не изящно, особенно в случае 32 и 64-битных контейнеров - десятичные значения становятся сумасшедшими большими. Можно легко сгенерировать маски с выражениями, которые компилятор может эффективно разрешить во время компиляции:

  • прочитайте маску для "mask and shift": ((1 << fieldLength) - 1) << (fieldIndex - 1), считая, что индекс в первом бите равен 1 (не ноль)
  • прочитайте маску для "shift and mask": (1 << fieldLength) - 1 (индекс здесь не играет роли, поскольку он всегда смещается в первый бит
  • маска записи: просто инвертируйте выражение маски "маска и смена" с помощью оператора ~

Как это работает (с 3-битным полем, начинающимся со второго бита из приведенных выше примеров)?

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

Те же примеры применимы к более широким целям и произвольной ширине и положению бит, при этом значения сдвига и маски меняются соответственно.

Также обратите внимание, что в примерах предполагается целое число без знака, которое вы хотите использовать, чтобы использовать целые числа в качестве переносимого альтернативного битового поля (обычные битовые поля никоим образом не гарантируются стандартным переносом), оба слева и правый сдвиг вставляет прокладку 0, что не относится к праву, сдвигающему целое число со знаком.

Еще проще:

Используя этот набор макросов (но только в С++, поскольку он полагается на генерацию функций-членов):

#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); }

Вы можете найти что-то простое:

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

И добавьте битовые поля как свойства, к которым вы можете легко получить доступ:

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

Замените decltype на gcc typeof pre-С++ 11.

Ответ 2

Вам нужно сдвинуть и замаскировать значение, например,...

Если вы хотите прочитать первые два бита, вам просто нужно замаскировать их так:

int value = input & 0x3;

Если вы хотите компенсировать это, вам нужно сдвинуть правые N бит, а затем замаскировать нужные биты:

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

Чтобы прочитать три бита, как вы задали в своем вопросе.

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

Ответ 3

Вам нужно выполнить операцию shift и mask (AND). Пусть b - любой байт, а p - индекс ( >= 0) бит, из которого вы хотите взять бит n ( >= 1).

Сначала вам нужно сдвинуть вправо b на p раз:

x = b >> p;

Во-вторых, вы должны замаскировать результат с помощью n:

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

Вы можете поместить все в макрос:

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

Ответ 4

"Как я, например, читаю 3-битное целочисленное значение, начиная со второго бита?"

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;

(я предположил, что "второй бит" - бит # 2, т.е. третий бит действительно.)

Ответ 5

Для чтения байтов используйте std:: bitset

const int bits_in_byte = 8;

char myChar = 's';
cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);

Для написания вам нужно использовать битовые операторы, такие как и ^ | и < → . не забудьте узнать, что они делают.

Например, чтобы иметь 00100100, вам нужно установить первый бит в 1 и сдвинуть его с помощью < → операторы 5 раз. если вы хотите продолжить писать, вы просто продолжаете устанавливать первый бит и сдвигать его. это очень похоже на старую пишущую машинку: вы пишете и переносите бумагу.

Для 00100100: установите первый бит в 1, сдвиг 5 раз, установите первый бит в 1 и сдвиньте 2 раза:

const int bits_in_byte = 8;

char myChar = 0;
myChar = myChar | (0x1 << 5 | 0x1 << 2);
cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);

Ответ 6

просто используйте это и 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     **/

например:

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

Ответ 7

int x = 0xFF;   //your number - 11111111

Как я, например, читаю 3-битное целочисленное значение, начиная со второго бита

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

Ответ 8

Если вы продолжаете извлекать биты из своих данных, вы можете использовать битполе. Вам просто нужно создать структуру и загрузить ее только с одним и нулями:

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

а затем загрузите его так (заменяя char на int или любые другие данные, которые вы загружаете):

long int i;
int j, k;
unsigned char c, d;

bitstream=malloc(sizeof(struct bitfield)*charstreamlength*sizeof(char));
for (i=0; i<charstreamlength; i++){
    c=charstream[i];
    for(j=0; j < sizeof(char)*8; j++){
        d=c;
        d=d>>(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;
        }
    }
}

Затем введите элементы доступа:

bitstream[bitpointer].bit=...

или

...=bitstream[bitpointer].bit

Все это предполагает, что работают на i86/64, а не на руке, так как рука может быть большой или маленькой endian.