Ответ 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.