Пример определения битовых полей (bit-fields) в классе:
struct PackedData {
unsigned char flag1 : 1; // Биты 0
unsigned char flag2 : 1; // Биты 1
unsigned char value : 6; // Биты 2-7
};
Здесь хорошо виден синтаксис объявления битового поля через одинарное двоеточие.
Ниже перечислены особенности битовых полей:
- Все члены существуют одновременно
- Каждый член занимает свою часть битов
- Размер оптимизирован под общую сумму битов
В примере выше, в памяти будет выделен один 8-битовый байт, то есть, компилятор упаковывает биты автоматически. Когда происходит вышеуказанное объявление, то компилятор распределяет биты последовательно (внутри одного или нескольких байтов). Визуально это можно представить так:
Байт в памяти:
[7][6][5][4][3][2][1][0] ← биты
│ │ │ │ │ │ │ └── flag1 (бит 0)
│ │ │ │ │ │ └───── flag2 (бит 1)
│ │ │ │ │ └──────── (не используется)
└──┴──┴──┴──┴─────────── value (биты 2-7)
Визуализация размещения:
Память: | value (6 бит) | flag2 (1 бит) | flag1 (1 бит) |
Биты: | 7 6 5 4 3 2 | 1 | 0 |
Стандарт C++ явно позволяет компилятору размещать несколько битовых полей в одной единице памяти (в байте/слове). Когда происходит обращение к полям, компилятор под капотом генерирует специальные инструкции. Все это вместе позволяет достаточно просто устанавливать и считывать биты без применения масок и всяких сдвиговых инструкций. Код становится очень простым:
PackedData data;
data.flag1 = 1; // Компилятор генерирует: установить бит 0 в 1
data.value = 42; // Компилятор генерирует: записать в биты 2-7 число 42
"Наезжание" битовых полей в пределах одного байта происходит не как в union (перекрытие целых полей), а как последовательное распределение битов внутри единицы памяти, и это происходит под управлением компилятора.
Если битовых полей много, и они хранятся в структуре с другими данными, то работают правила размещения битовых полей:
- Битовые поля группируются в "юниты" (обычно размером с int)
- При смене типа или при заполнении юнита - начинается новый юнит
- Выравнивание может добавлять padding-байты
Это значит, что следующий тип занимает 3 байта:
struct PackedData {
unsigned char firstBits1 : 1; // Байт 0, биты 0-1
unsigned char firstBits2 : 1;
char a; // Байт 1 (может быть байт 2 из-за выравнивания)
unsigned char secondBits1 : 1; // Байт 2 для битовых полей, биты 0-1
unsigned char secondBits2 : 1;
};
Но с учетом выравнивания, может быть добавлен "пустой" 4-й байт, чтобы вся структура влезала в int.