ПрограммированиеEmbedded/Low-level C разработчик

Объясните, как работают объединения (union) в языке C. Какие задачи решаются с их помощью, как правильно ими пользоваться, и какие типичные подводные камни встречаются при их использовании?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Union (объединение) — это специальный тип данных, хранящий разные значения в одной и той же области памяти. В union все члены располагаются в памяти по одному и тому же адресу, и объем памяти равен размеру самого большого члена.

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

  • удобно для экономии памяти, если переменная в один момент времени принимает один из нескольких типов значений;
  • позволяет интерпретировать одни и те же биты разными способами (например, для low-level access или протоколов).

Пример:

union Data { int i; float f; char s[4]; }; union Data d; d.i = 0x41424344; // в памяти теперь 4 байта, которые можно читать как int, float, строку printf("%c%c%c ", d.s[0], d.s[1], d.s[2]); // вендор-специфический вывод

Подводные камни и правила использования:

  • Держите в union в каждый момент времени только одно "значимое" поле.
  • Нет автоматического отслеживания, какое поле было "активным" последним.
  • Чтение неактивного поля — undefined behavior.
  • Очень важен порядок байтов на платформе (endian!)

Вопрос с подвохом

Вопрос: Каков смысл использования union, когда можно просто использовать структуру из нескольких полей?

Ответ: Использование union экономит память, потому что в любой момент внутри хранится только ОДНО значение, а не все сразу. В структуре для каждого поля выделяется память, а в union — только для максимального из полей, остальные "разделяют" эту память. Также union позволяет выполнять безопасное или намеренное преобразование между разными представлениями данных на одном фрагменте памяти.

Пример:

struct S { int i; float f; } s; // sizeof = sizeof(int) + sizeof(float) union U { int i; float f; } u; // sizeof = max(sizeof(int),sizeof(float))

Примеры реальных ошибок


История

В проекте драйвера устройства связь с "железом" велась через union с битовым доступом к данным. После небольшого рефакторинга разработчик начал писать не в то поле union, что привело к чтению неактуальных данных и фатальным сбоям системы, потому что в union только одно поле "настоящее" в каждый момент.


История

При обмене сетевыми пакетами через union для управления памятью разработчик забыл учесть выравнивание структур. В результате произошло смещение на один байт, и структура парсилась с неверными смещениями, что делало протокол несовместимым с оригиналом.


История

В работе над библиотекой сериализации программист передавал union по значению в функцию, где не инициализировалось нужное поле перед чтением. Из-за этого данные сериализовались некорректно, возникал мусор в выходном потоке, и восстановить исходную информацию было невозможно.