View Categories

Объединения

Объединение (union) позволяет хранить несколько переменных в одной области памяти. Объявляется так же, как структура, но с использованием ключевого слова union:

union Union {
    int a;      // 4 байта
    float b;    // 4 байта
};

Union u;    // выделилась память 4 байта - ОДНА НА ВСЕХ

Отличие объединения от структуры состоит в том, что все переменные привязаны к одной общей области памяти: записав значение в одну переменную, мы автоматически записываем его во все остальные в бинарном виде. Объединения могут использоваться для "экономии места" - создали одну ячейку и используем её как разные типы по очереди:

u.a = 123;
u.b = 3.14;

Что по сути бессмысленно - сейчас даже у дешёвых и слабых МК достаточно памяти, чтобы так не изголяться

Интересное начинается, когда мы решим прочитать переменную, которая была записана не последней:

u.b = 3.14;
// здесь u.a == 1078523331

Таким образом мы получили доступ к памяти через другой тип, не тот, который был записан. В примере выше получилось целочисленное представление float числа 3.14.

Распаковка и запаковка #

Всё станет ещё интереснее, если подключить сюда структуры и/или массивы - можно очень быстро конвертироваться между любыми типами данных в бинарном виде!

union {
    uint32_t u32;
    uint8_t bytes[4];
} u;

u.u32 = 0xaabbccdd;

u.bytes[0];     // == 0xdd
u.bytes[1];     // == 0xcc
u.bytes[2];     // == 0xbb
u.bytes[3];     // == 0xaa
// порядок зависит от endianness архитектуры

Объединения позволяют писать очень эффективные по скорости выполнения, а самое главное - очень читаемые алгоритмы для разбора бинарных данных. Например, нам приходит пакет данных, запакованных следующим образом:

file

Данные приходят в виде условного массива - просто несколько байт. Чтобы распаковать такой пакет, конечно можно использовать битовые операции: посдвигать, повыделять по маске, поскладывать и в итоге записать в отдельные переменные, чтобы оно было читаемо в программе - отличная задачка для ума. Но довольно многословная и неэффективная. Более того, если протокол изменится - это всё придётся переписывать заново!

Гораздо красивее будет сделать структуру с битовыми полями, запаковать её, чтобы она не выравнивалась с дырками, создать на её основе union в паре с массивом и... просто получить результат!

struct __attribute__((packed)) Packet {
    uint8_t id : 3;
    uint8_t address : 5;

    uint8_t n1 : 2;
    uint8_t n2 : 2;
    uint8_t n3 : 4;

    uint8_t f1 : 1;
    uint8_t f2 : 1;
    uint8_t f3 : 1;
    uint8_t f4 : 1;
    uint8_t f5 : 1;
    uint8_t f6 : 1;
    uint8_t f7 : 1;
    uint8_t f8 : 1;

    uint8_t crc;
};

union Unpack {
    Packet p;
    uint8_t bytes[4];
};

Обратите внимание на порядок полей - он обратный, так как поля перечисляются от младших к старшим битам

    Unpack up;
    // записываем байты в up.bytes. Для наглядности - вручную, смотрите таблицу протокола
    up.bytes[0] = 0b11100000;   // id: 111, address: 0
    up.bytes[1] = 0b11001111;   // n1: 11, n2: 0, n3: 1111
    up.bytes[2] = 0b10100101;
    up.bytes[3] = 0b11111111;

    up.p.id;        // 111
    up.p.address;   // 0
    up.p.n1;        // 11
    up.p.n2;        // 0
    up.p.n3)        // 111
    up.p.f1;        // 1
    up.p.f2;        // 0
    up.p.crc;       // 11111111

Это работает и в обратную сторону - можно записать нужные значения в структуру и получить её в виде набора байтов. Передаём на другое устройство - и оно точно так же их распаковывает.

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

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