В программировании часто используются "флаги" - логические значения. Тип bool
у нас занимает 1 байт (минимальная адресуемая ячейка физической памяти), то есть 8 бит. Расточительно! Рассмотрим конструкции, позволяющие упаковать биты в байты.
Битовые поля #
В структурах и классах можно создавать переменные с произвольным количеством бит - битовые поля:
struct Flags {
uint8_t f0 : 1;
uint8_t f1 : 1;
uint8_t f2 : 1;
uint8_t f3 : 1;
uint8_t f4 : 1;
};
Flags f;
f.f0 = 1;
f.f1 = 0;
f.f2 = 1;
Упаковка флагов #
Можно "запаковать" однобитные флаги в целочисленную переменную (8, 16, 32 бит):
uint8_t flags = 0;
flags |= (1 << 3); // установить флаг #3
flags &= ~(1 << 3); // сбросить флаг #3
flags & (1 << 3); // прочитать флаг #3
Для удобства можно задефайнить флаги как:
#define FLAG_0 (1 << 0)
#define FLAG_1 (1 << 1)
#define FLAG_2 (1 << 2)
flags |= FLAG_2;
flags &= ~FLAG_2;
Упаковка данных #
Можно упаковать данные размером в несколько бит в нужное место в другой переменной, вот например моя реализация макросов:
#define GET_BITS(x, from, len) (((x) & (((1ul << (len)) - 1) << (from))) >> (from))
#define SET_BITS(x, bits, from, len) x = (x & ~(((1ul << (len)) - 1) << (from))) | ((uint32_t)(bits) << (from))
SET_BITS
не берёт значение по маске для экономии времени выполнения, считается что размер данных не превышает указанную длину. Использование:
uint32_t b = 0;
SET_BITS(b, 0b1101, 20, 4);
Serial.println(GET_BITS(b, 20, 4), BIN);
Serial.println(b, BIN);
По тестам данная конструкция оказалась медленнее битовых полей, так что на практике её не использовал.
Библиотеки #
У меня есть библиотека BitPack, она позволяет паковать флаги в байтовый массив. Но самый быстрый и оптимальный вариант - BitFlags
(ссылка на исходник) - идёт вместе с BitPack
. Очень компактная реализация, позволяет читать, писать и проверять по несколько флагов за раз - код сильно оптимизируется компилятором и сворачивается до пары инструкций. Пример:
#include <BitFlags.h>
#define MY_FLAG_0 bit(0)
#define KEK_FLAG bit(1)
#define SOME_F bit(2)
void setup() {
Serial.begin(115200);
BitFlags8 flags;
flags.set(KEK_FLAG | SOME_F); // установить два флага
Serial.println(flags.read(KEK_FLAG | SOME_F)); // стоит один из флагов
Serial.println(flags.isSet(KEK_FLAG | SOME_F)); // стоят все флаги
// операция compare берёт маску по первому аргументу и сравнивает со вторым
// фактически смысл такой: определение ситуации, когда из указанных флагов подняты только определённые
// здесь - из флагов KEK_FLAG и SOME_F поднят только SOME_F (KEK_FLAG опущен)
Serial.println(flags.compare(KEK_FLAG | SOME_F, SOME_F));
}
void loop() {
}
#include <BitFlags.h>
enum class Flags {
f1 = bit(0),
f2 = bit(1),
f3 = bit(2),
};
void setup() {
Serial.begin(115200);
BitFlags8 f;
f.set(Flags::f1);
f.set(Flags::f2);
Serial.println(f.read(Flags::f1));
Serial.println(f.read(Flags::f2));
Serial.println(f.read(Flags::f3));
}
void loop() {
}