Страницы

Поиск по вопросам

четверг, 5 декабря 2019 г.

Где хранятся куски union? C (Си)

#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 | ______________________________

Комментариев нет:

Отправить комментарий