Объединения в встроенном C: эффективное совместное использование памяти и обмен сообщениями
Понимание объединений во встроенном C
Во встроенных системах объединение — мощная структура данных, позволяющая хранить разные типы в одной и той же области памяти. Статья разбирает основы объединений, показывает практические примеры и объясняет, почему они незаменимы в приложениях с жесткими ограничениями по памяти и пропускной способности.
Разница между структурой и объединением во встроенном C
Ранее мы рассматривали структуры — способ группировки разнородных переменных в единую сущность. В C объединения — дополнительная конструкция, также группирующая разные типы, но с ключевым отличием: каждый член использует один и тот же адрес памяти.
Вводный пример
Объявление объединения почти идентично структуре; нужно просто заменить struct на union:
union test { uint8_t c; uint32_t i; };
Шаблон определяет два члена: c занимает один байт, i — четыре байта. Чтобы создать переменную этого типа:
union test u1;
Доступ к членам через точечный оператор:
u1.i = 10; m = u1.c; // m должен быть uint8_t
Так как размер объединения равен размеру его наибольшего члена, u1 занимает четыре байта. Запись в i перезаписывает ту же память, которую использует c, поэтому члены взаимоисключающи.
Нужно ли нам совместное использование памяти?
Представьте два встроенных устройства, Устройство А и Устройство Б, обменивающиеся данными о статусе, скорости и позиции (см. Рисунок 1). Элементы данных и их размеры:

Рисунок 1
| Переменная | Размер (байт) | Значение |
|---|---|---|
| power | 1 | Заряд батареи |
| op_mode | 1 | Режим работы |
| temp | 1 | Температура окружающей среды |
| x_pos | 2 | Координата X |
| y_pos | 2 | Координата Y |
| vel | 2 | Скорость |
Если Устройству Б нужна вся информация, простая структура потребовала бы 9 байт, и каждый цикл связи передавал бы 9-байтовый кадр (Рисунок 2). Однако во многих реальных сценариях три типа сообщений взаимоисключающи: иногда только статус, иногда только позиция, иногда только скорость. В этом случае максимальная полезная нагрузка — 4 байта (сообщение позиции). Объединение, разделяющее 4-байтовый буфер, может хранить любое из трех сообщений, сокращая размер кадра с 9 до 4 байт (Рисунок 3).

Рисунок 2

Рисунок 3
Использование объединений для пакетов сообщений
Создадим две структуры — для статуса и позиции — и поместим их в объединение вместе с полем скорости:
struct status_s { uint8_t power; uint8_t op_mode; uint8_t temp; }; struct position_s { uint16_t x_pos; uint16_t y_pos; }; union msg_union { struct status_s status; struct position_s position; uint16_t vel; };
Размер объединения — 4 байта (размер position_s), разделяемых между всеми тремя типами сообщений.
Как отслеживать активный член объединения
Когда приемник читает объединение, он должен знать, какой член действителен. Распространенный паттерн — помеченное объединение: отдельное поле, записывающее активный член. Добавим однобайтовый msg_type перед объединением:
struct message_s { uint8_t msg_type; // 's' = статус, 'p' = позиция, 'v' = скорость union { struct status_s status; struct position_s position; uint16_t vel; } msg; };
Передавая байт msg_type вместе с объединением, получатель может правильно интерпретировать полезную нагрузку без предварительного знания её формы.
Альтернативное решение: динамическое выделение памяти
Другой подход — выделять память на лету для каждого типа сообщения. Передатчик устанавливал бы msg_type и выделял ровно столько байт, сколько нужно для этой полезной нагрузки. Приемник интерпретировал бы входящие данные на основе msg_type и освобождал память по завершении. Хотя это может быть чуть более эффективно по памяти, динамическое выделение вносит накладные расходы во время выполнения и требует явного освобождения, поэтому многие встроенные разработчики предпочитают статическую стратегию с объединением.
Далее: применение объединений
Объединения также часто используются для извлечения меньших частей данных из более крупного слова — техника, особенно ценная при разборе коммуникационных протоколов или аппаратных регистров. Следите за следующей статьей серии, где мы погрузимся в эти сценарии.
Для просмотра других статей по встроенному C посетите указатель статей.
Встроенный
- Выбор идеального языка программирования для решений Промышленного IoT
- Основы программирования микропроцессоров
- Использование данных в Интернете надежности: стратегии эффективного управления
- Демократизация Интернета вещей: Next-Gen Satellite IoT приносит универсальную, доступную связность
- 9 языков программирования, которые обязательно нужно выучить в 2021 году
- Объединения в C (union): эффективное использование памяти для разных типов данных
- Будущее центров обработки данных: тренды, технологии и устойчивость
- Использование облачной мощи для IoT: раскрытие бесшовной связности и инсайтов данных
- Понимание программирования роботов: методы объяснены
- Единицы измерения компрессора: понятное руководство по распространенным терминам