Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> Язык C

Битовые поля в C: компактное хранение флагов и малых целых чисел

Зачем нужны битовые поля?

Битовые поля (bit fields) позволяют упаковать несколько небольших целочисленных значений в одно машинное слово, экономя память. Часто используются для:

Синтаксис

struct {
    тип имя : ширина; // ширина в битах
};

Пример: флаги состояния

struct FileFlags {
    unsigned int readable   : 1;
    unsigned int writable   : 1;
    unsigned int executable : 1;
    unsigned int is_dir     : 1;
    unsigned int reserved   : 28; // заполнение до 32 бит
}; // размер: 4 байта (вместо 4х int = 16 байт)

struct FileFlags f = { .readable = 1, .writable = 1 };
if (f.readable && f.writable) { /* read-write */ }

Пример: компактное хранение даты

struct Date {
    unsigned int day   : 5;  // 1-31 (5 бит = 0-31)
    unsigned int month : 4;  // 1-12 (4 бита = 0-15)
    unsigned int year  : 12; // 0-4095 (12 бит)
    // всего 21 бит → упаковано в 4 байта (32 бита)
};

Правила и ограничения

  1. Нельзя взять адрес битового поля: &f.readable — ошибка компиляции.
  2. Массивы битовых полей запрещены.
  3. Порядок размещения бит в памяти — implementation-defined (обычно от младших к старшим или наоборот, зависит от endianness и ABI). Не переносим между платформами.
  4. Выравнивание: битовые поля не пересекают границу своего базового типа (обычно int/unsigned int). Новое поле начинается в следующем слове, если не помещается.
  5. Безымянные поля (пропуски): unsigned int : 3; — 3 бита padding. : 0 — выравнивание на следующую границу слова.
  6. Знаковые битовые поля — поведение отрицательных значений implementation-defined (обычно two's complement). Лучше использовать unsigned.

Пример: демонстрация размера и упаковки

#include <stdio.h>

struct Flags1 {
    unsigned int a : 1;
    unsigned int b : 1;
    unsigned int c : 1;
};

struct Flags2 {
    unsigned int a : 1;
    unsigned int b : 1;
    unsigned int c : 1;
    unsigned int d : 1;
    // ... 32 поля по 1 биту
    unsigned int z : 1;
};

struct Flags3 {
    unsigned int a : 1;
    unsigned int b : 1;
    unsigned int c : 1;
    unsigned int : 0; // принудительное выравнивание на след. int
    unsigned int d : 1;
};

int main() {
    printf("Flags1 (3 бита): %zu байт\n", sizeof(struct Flags1)); // 4
    printf("Flags2 (32 бита): %zu байт\n", sizeof(struct Flags2)); // 4
    printf("Flags3 (с :0): %zu байт\n", sizeof(struct Flags3));   // 8 (два int)
    return 0;
}

Инициализация и использование

struct Config {
    unsigned int enabled : 1;
    unsigned int level   : 3; // 0-7
    unsigned int mode    : 2; // 0-3
} cfg = { .enabled = 1, .level = 5, .mode = 2 };

cfg.level = 7; // OK
// cfg.level = 8; // UB: значение не помещается в 3 бита (обычно обрезается модуль 8)
printf("enabled=%u, level=%u, mode=%u\n", cfg.enabled, cfg.level, cfg.mode);

Битовые поля vs битовые операции

ПодходПлюсыМинусы
Битовые поляЧитаемый код, именованные поля, типо-безопасностьНепереносимый layout, нет адреса, ограниченные типы
Битовые маски (&, |, ^, <<)Полный контроль, переносимо, работает с любыми типамиМагические числа,易出错, менее читаемо

Рекомендация: для внутренних флагов структуры — битовые поля. Для протоколов/файлов/железа — явные маски и сдвиги (или packed struct + битовые поля с осторожностью).

C23 улучшения

Пример: регистр периферии (embedded)

// Регистр управления UART (гипотетический)
union UART_CTRL {
    uint32_t reg;
    struct {
        unsigned int enable     : 1; // бит 0
        unsigned int tx_enable  : 1; // бит 1
        unsigned int rx_enable  : 1; // бит 2
        unsigned int parity_en  : 1; // бит 3
        unsigned int parity_odd : 1; // бит 4
        unsigned int stop_bits  : 1; // бит 5 (0=1 stop, 1=2 stop)
        unsigned int data_bits  : 2; // биты 6-7 (0=5, 1=6, 2=7, 3=8)
        unsigned int reserved   : 24; // биты 8-31
    } bits;
};

// Использование:
union UART_CTRL ctrl = { .reg = 0 };
ctrl.bits.enable = 1;
ctrl.bits.tx_enable = 1;
ctrl.bits.rx_enable = 1;
ctrl.bits.data_bits = 3; // 8 бит
// Запись в регистр: UART->CTRL = ctrl.reg;

Язык C

  1. Освоение побитовых и сдвиговых операторов в C#: полное руководство
  2. Четыре проверенных паттерна пользовательских функций в C
  3. Как передавать одномерные и двумерные массивы в функции в C — практическое руководство
  4. Освоение структур и указателей в C: практическое руководство
  5. Ключевое слово this в Java: практическое применение, конструкторы и примеры
  6. Инкапсуляция в Java: практическое руководство по скрытию данных
  7. Структуры (struct) в C++: объяснение с практическим примером
  8. Функции в C: объявление, определение, вызов, аргументы (по значению и по ссылке), рекурсия
  9. Функции с переменным числом аргументов в C: практическое руководство
  10. Интегрирование в MATLAB: от неопределенных интегралов к суммам Римана