4,7K подписчиков

Объединения в языке C

106 прочитали

В предыдущей части:

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

Объединения

Объединение (union) позволяет объединить разные описания для одних и тех же данных.

К примеру, структуру цветного пиксела можно описать так:

struct Pixel { int value; };

Технически это даже не структура, так как она содержит всего одно поле. Но оставим её для примера. Ещё одну структуру можно описать так:

struct Pixel { char r; char g; char b; char a; };

Это тоже описание цвета пиксела, но разбитого на компоненты RGBA.

Работая с видеопамятью, в некоторых случаях было бы удобно обращаться к общему значению pixel.value, а в некоторых – к индивидуальным компонентам pixel.r, pixel.g, и т.д. Объединение позволяет это сделать:

В предыдущей части: Обсуждалось, как можно содержимое одной структуры подменить содержимым другой. Это делалось через "указатель-хак", но в языке есть и легитимный способ.

Объявление объединения похоже на структуру, но есть нюанс. Каждое поле в объединении описывает не отдельную порцию данных, а альтернативное представление тех же самых данных. Именно поэтому я не смог объявить там отдельные поля r, g, b, a – каждое из них стало бы не отдельным полем, а просто новым представлением того же самого поля. Вместо этого пришлось сделать массив rgba[4]. Таким образом, представление value это то же самое, что rgba[4].

Проблему представления отдельных полей решим позже, а пока посмотрим, как работает объединение.

union Pixel p;
p.value = 5;
p.rgba[0] = 5;

Мы можем обращаться как к p.value, так и к p.rgba[0]. При этом p.rgba[0] находится там же, где находится младший байт p.value. Значение 5 запишется в один и тот же байт памяти. Но так как value это 32-битное число, то дополнительно будут обнулены старшие 3 байта. Этого не произойдёт при записи в rgba[0], так как там меняется только один байт.

Размеры данных, описываемых альтернативными вариантами, не обязательно должны совпадать. К примеру, мы можем объявить такое объединение:

В предыдущей части: Обсуждалось, как можно содержимое одной структуры подменить содержимым другой. Это делалось через "указатель-хак", но в языке есть и легитимный способ.-2

Здесь одно представление занимает один байт, а другое – 100 байт. Как нетрудно догадаться, размер объединения равен размеру самого большого из представлений.

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

Устранить их довольно легко, назначив в качестве представления структуру:

В предыдущей части: Обсуждалось, как можно содержимое одной структуры подменить содержимым другой. Это делалось через "указатель-хак", но в языке есть и легитимный способ.-3

В объединении появилось представление в виде поля rgba, тип которого это безымянная структура с полями r, g, b, a.

Теперь мы можем пользоваться объединением так:

union Pixel p;
p.rgba.r = 5;

Промежуточное звено rgba всё-таки никуда не делось. Жаль, что нельзя написать просто:

p.r = 5;

Но таковы правила.

Имея опыт программирования на JS, Python, PHP, в общем любом языке с динамическими полями объектов, можно подумать, что обращения через промежуточные поля вроде p.rgba.*.*.* могут замедлить программу. На самом деле можно создать хоть тысячу промежуточных полей (структуры внутри структур и т.п.), для языка C это будет всего лишь смещение от базового адреса, которое рассчитывается на этапе компиляции. Так что кроме визуального перегруза это ничем не грозит.

В заключение напомню, что объединения это тоже типы, и поэтому подчиняются точно тем же правилам синтаксиса, что и структуры. Тип переменной-объединения должен начинаться с union ..., объединения могут быть безымянные или поименованные, и им также можно назначать алиасы с помощью typedef.

Читайте дальше: