View Categories

Битовые флаги

В программировании часто используются "флаги" - логические значения. Тип 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() {
}
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх