Как установить определенные биты?

Допустим, у меня есть переменная uint16_t, в которой я должен установить определенные биты.

Пример:

uint16_t field = 0;

Это означало бы, что все биты равны нулю: 0000 0000 0000 0000

Теперь я получаю некоторые значения, которые мне нужно установить в определенных позициях.

val1=1; val2=2, val3=0, val4=4, val5=0;

Структура установки битов следующая.

0|000|  0000| 0000 000|0

val1 должен быть установлен первым битом слева. так что это только один или ноль.

val2 должен быть установлен в следующих трех битах. val3 в следующих четырех битах. val4 в следующих семи битах и ​​val5 в одном последнем бите.

Результат будет таким: 1010 0000 0000 1000

Я только узнал, как один конкретный бит, но не «группы». (сдвиг или набор битов)

Кто-нибудь знает, как решить эту проблему?


person baam    schedule 27.06.2013    source источник
comment
является ли структура фиксированной или вариативной?   -  person user1810087    schedule 27.06.2013


Ответы (5)


Есть (по крайней мере) два основных подхода. Можно было бы создать структуру с некоторыми битовыми полями:

struct bits { 
    unsigned a : 1;
    unsigned b : 7;
    unsigned c : 4;
    unsigned d : 3;
    unsigned e : 1;
};

bits b;

b.a = val1;
b.b = val2;
b.c = val3;
b.d = val4;
b.e = val5;

Чтобы получить 16-битное значение, вы можете (например) создать объединение этой структуры с uint16_t. Только одна небольшая проблема: стандарт не гарантирует, в каком порядке окажутся битовые поля, когда вы посмотрите на 16-битное значение. Просто, например, вам может понадобиться изменить порядок, который я дал выше, чтобы получить порядок от наиболее значимых до наименее значимых битов, которые вам действительно нужны (но смена компилятора может снова все испортить).

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

int16_t result = val1 | (val2 << 1) | (val3 << 8) | (val4 << 12) | (val5 << 15);

На данный момент я предположил, что каждый из входных данных начинается в правильном диапазоне (т. е. имеет значение, которое может быть представлено в выбранном количестве бит). Если есть вероятность, что это может быть неправильно, вы должны сначала замаскировать его до правильного количества битов. Обычный способ сделать это примерно так:

uint16_t result = input & ((1 << num_bits) - 1);

Если вам интересна математика, она работает следующим образом. Предположим, мы хотим гарантировать, что ввод умещается в 4 бита. Сдвиг 1 влево на 4 бита дает 00010000 (в двоичном формате). Вычитание из него единицы затем очищает один установленный бит и устанавливает все менее значимые биты, что дает 00001111 для нашего примера. Это дает нам набор первых наименее значащих битов. Когда мы делаем побитовое AND между этим и входными данными, любые старшие биты, которые были установлены во входных данных, очищаются в результате.

person Jerry Coffin    schedule 27.06.2013
comment
Пробовал с битовыми полями. Компилятор не изменится, поэтому я мог бы использовать это решение. Я создаю объединение следующим образом: struct bits { union{ unsigned a : 1; unsigned b : 7; unsigned c : 4; unsigned d : 3; unsigned e : 1; }vals; }; и устанавливаю значения `bits c; c.vals.a = знач1; ...` но как мне теперь получить результат в виде одного полного числа? - person baam; 27.06.2013
comment
@baam: вам нужны два элемента в объединении - структура и uint16_t. union { bits b; uint16_t v; }; Затем вы запишете данные в u.b.whatever, но получите все 16-битное число как u.v; - person Jerry Coffin; 27.06.2013

Одним из решений было бы установить K-битное значение, начинающееся с N-го бита field, как:

uint16_t value_mask = ((1<<K)-1) << N; // for K=4 and N=3 will be 00..01111000
field = field & ~value_mask; // zeroing according bits inside the field
field = field | ((value << N) & value_mask); // AND with value_mask is for extra safety

Или, если вы можете использовать struct вместо uint16_t, вы можете использовать битовые поля и позволить компилятору выполнять все эти действия за вас.

person nullptr    schedule 27.06.2013

Для этого можно использовать побитовые операторы или и сдвиг.

Используйте сдвиг << для «перемещения байтов влево»:

int i = 1;  // ...0001
int j = i << 3 // ...1000

Затем вы можете использовать побитовое или |, чтобы поместить его в нужное место (при условии, что у вас есть все нули в битах, которые вы пытаетесь перезаписать).

int k = 0;  // ...0000
k |= i // ...0001
k |= j // ...1001

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

person Philipp Matthias Schäfer    schedule 27.06.2013

попробуйте этот код:

uint16_t shift(uint16_t num, int shift)
{
    return num | (int)pow (2, shift);
}

где shift - это позиция бита, которую вы хотите установить

person DreamChild    schedule 27.06.2013

person    schedule
comment
Нет необходимости в присвоении 0. - person xxbbcc; 27.06.2013