#c #память #union
#include#include union IFC { int i; float f; char c; }; int main(void) { union IFC ifc = {.f = 3.14}; printf("Union's float is %.2f\n", ifc.f); // 3.14 ifc.i = 2; printf("Union's integer is %d\n", ifc.i); // 2 ifc.c = 'A'; printf("Union's character is %c\n", ifc.c); // A printf("Union's float is %.2f\n", ifc.f); // 0.00 return 0; } Как данный код работает на уровне памяти вычислительной машины? Откуда берётся значение 0.00 у inf.f в то время, когда в union'e используется inf.c? Я понимаю, что один union занимает в любом допустимом для него состоянии (в моём случае int/float/char) одинаковое количество байт. Но ведь эти байты хранят только одно значение. Почему же, если в данный момент времени там хранится символ 'A', я всё равно могу обратиться к union'у за вещественным числом inf.f? Хоть оно и не то, что я когда-то задавал, хоть оно и равняется всегда нулю, оно всё же возвращается. Как это работает?
Ответы
Ответ 1
Во-первых, язык С гарантирует, что все поля union имеют один и тот же адрес, совпадающий с адресом всего union. Практически это означает, что все поля union хранятся с перекрытием по одному и тому же адресу в памяти. Размер union будет равен размеру его максимального поля (плюс, возможно, какие-то дополнительные неиспользуемые байты, добавленные для целей выравнивания). Во-вторых, язык С говорит, что назначение значения какому-то полю union приводит неиспользуемые этим полем байты в неопределенное состояние. По этой причине, так как поле char c на вашей платформе скорее всего имеет меньший размер, чем поле float f, нет смысла читать поле f после назначения значения полю c - это приведет в общем случае к неопределенному поведению. На практике вполне разумно предположить, что даже в рамках одной программы одна запись значения c может затронуть все байты вашего union, а другая - только байты собственно c. Union в С можно использовать для переинтерпретации объектного представления одного типа как объектного представления другого типа, но это обычно имеет смысл только для типов одинакового размера. Простой эксперимент с GCC показывает (https://godbolt.org/z/nphzx-), что в неоптимизированном коде ваш union хранится в памяти и изменение значения поля c изменяет лишь один байт памяти, а вот в оптимизированном коде весь union хранится в регистре процессора esi и запись значения поля c переписывает всё содержимое регистра, т.е. фактически обнуляет все "лишние" байты union.Ответ 2
Биты всех полей занимают одну и ту же память. Их интерпретация зависит от имени (типа) поля, к которому вы обращаетесь. Т.о. изменяя любое поле вы одновременно изменяете и все остальные.Ответ 3
Как это работает? Очень часто union используются для работы с "железом" - чтение, проверка и запись бит в слово. Допустим, Вы пишите драйвер для устройства, который должен работать с множеством регистров этого устройства, а каждый регистр - совокупность множества битовых полей. Проще всего это реализуется так: union RESISTER_1 { u32 int_value; struct { uint field1 : 1; uint field2 : 7; uint field3 : 1 . . . } bit_fields; } xyz; Тогда читать/писать значение регистра Вы сможете используя поле размером в слово (xyz.int_value), а работать с битами - используя битовые поля (xyz.bit_fields.field2).Ответ 4
Вот схема по которой все очень наглядно. union container { short s; int i; float f; char c; double d; long l; long long ll; }; схема расположения в памяти: container ______________________________ |char| | | short | | | int | | | float | | | double | | long long | | long | | 1 | 2 | 4 | 8 | ______________________________
Комментариев нет:
Отправить комментарий