Могу ли я присвоить значение одному члену объединения и прочитать то же значение из другого?

В основном у меня есть

struct foo {
        /* variable denoting active member of union */
        enum whichmember w;
        union {
                struct some_struct my_struct;
                struct some_struct2 my_struct2;
                struct some_struct3 my_struct3;
                /* let's say that my_struct is the largest member */
        };
};

main()
{
        /*...*/
        /* earlier in main, we get some struct foo d with an */
        /* unknown union assignment; d.w is correct, however */
        struct foo f;
        f.my_struct = d.my_struct; /* mystruct isn't necessarily the */
                                /* active member, but is the biggest */
        f.w = d.w;
        /* code that determines which member is active through f.w */
        /* ... */
        /* we then access the *correct* member that we just found */
        /* say, f.my_struct3 */

        f.my_struct3.some_member_not_in_mystruct = /* something */;
}

Доступ к членам C union через указатели, кажется, говорит о том, что доступ к членам через указатели в порядке. Смотрите комментарии.

Но мой вопрос касается прямого доступа к ним. По сути, если я напишу всю информацию, которая мне нужна, крупнейшему члену объединения и буду отслеживать типы вручную, будет ли доступ к указанному вручную члену по-прежнему давать правильную информацию каждый раз?

3 ответа

Решение

Я отмечаю, что код в вопросе использует анонимный союз, что означает, что он должен быть написан для C11; анонимные союзы не были частью C90 или C99.

ISO / IEC 9899: 2011, текущий стандарт C11, имеет следующее:

§6.5.2.3 Структура и члены профсоюза

Expression3 Постфиксное выражение с последующим . оператор и идентификатор обозначают член структуры или объединенного объекта. Значение соответствует названному члену (95) и является lvalue, если первое выражение является lvalue. Если первое выражение имеет уточненный тип, результат имеет уточненную версию типа указанного члена.

Expression4 Постфиксное выражение с последующим -> оператор и идентификатор обозначают член структуры или объединенного объекта. Это значение именованного члена объекта, на которое указывает первое выражение, и является lvalue.96) Если первое выражение является указателем на квалифицированный тип, результат имеет так называемую версию типа указанного члена.

№5 …

One6 Одна специальная гарантия сделана для того, чтобы упростить использование объединений: если объединение содержит несколько структур, которые имеют общую начальную последовательность (см. Ниже), и если объект объединения в настоящее время содержит одну из этих структур, разрешается проверять общая начальная часть любого из них везде, где видна декларация завершенного типа объединения. Две структуры имеют общую начальную последовательность, если соответствующие элементы имеют совместимые типы (и, для битовых полей, одинаковой ширины) для последовательности из одного или нескольких начальных элементов.


95) Если элемент, используемый для чтения содержимого объекта объединения, не совпадает с элементом, который последний раз использовался для хранения значения в объекте, соответствующая часть представления объекта значения повторно интерпретируется как представление объекта в новом type, как описано в 6.2.6 (процесс иногда называется 'type punning' '). Это может быть представление ловушки.

96) Если &E является допустимым выражением указателя (где & является оператором 'address-of' ', который генерирует указатель на свой операнд), выражением (&E)->MOS такой же как E.MOS,

Курсив как в стандарте

И раздел §6.2.6 Представления типов говорит (частично):

§6.2.6.1 Общие положения

When6 Когда значение сохраняется в объекте структуры или типа объединения, в том числе в объекте-члене, байты представления объекта, которые соответствуют любым байтам заполнения, принимают неопределенные значения.51) Значение структуры или объекта объединения никогда не является представлением ловушек, даже если значение члена структуры или объекта объединения может быть представлением ловушек.

When7 Когда значение сохраняется в элементе объекта типа объединения, байты представления объекта, которые не соответствуют этому элементу, но соответствуют другим элементам, принимают неопределенные значения.


51) Таким образом, например, при назначении структуры не нужно копировать какие-либо биты заполнения.


Моя интерпретация того, что вы делаете, заключается в том, что в сноске 51 говорится, что "это может не сработать", потому что вы, возможно, назначили только часть структуры. В лучшем случае вы наступаете на тонкий лед. Однако, напротив, вы оговариваете, что назначенная структура (в f.my_struct = d.my_struct; назначение) является крупнейшим членом. Возможности умеренно высоки, что это не пойдет не так, но если заполненные байты в двух структурах (в активном члене объединения и в самом большом члене объединения) находятся в разных местах, то все может пойти не так и если вы сообщаете о проблеме автору компилятора, он просто скажет вам "не нарушайте стандарт".

Таким образом, если я языковой адвокат, ответ этого языкового адвоката - "Это не гарантировано". На практике вы вряд ли столкнетесь с проблемами, но вероятность есть, и вы ни у кого не вернетесь.

Чтобы сделать ваш код безопасным, просто используйте f = d; с профсоюзным заданием.


Наглядный пример

Предположим, что машина требует double выровнен по 8-байтовой границе и sizeof(double) == 8, тот int должны быть выровнены на 4-байтовой границе и sizeof(int) == 4и что short должны быть выровнены на 2-байтовой границе и sizeof(short) == 2). Это правдоподобный и даже общий набор размеров и требований к выравниванию.

Далее, предположим, что у вас есть вариант структуры с двумя структурами в вопросе:

struct Type_A { char x; double y; };
struct Type_B { int a; short b; short c; };
enum whichmember { TYPE_A, TYPE_B };

struct foo
{
    enum whichmember w;
    union
    {
        struct Type_A s1;
        struct Type_B s2;
    };
};

Теперь, в соответствии с указанными размерами и выравниванием, struct Type_A будет занимать 16 байт, и struct Type_B будет занимать 8 байт, поэтому объединение будет также использовать 16 байт. Расположение союза будет таким:

+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| x | p...a...d...d...i...n...g |               y               |  s1
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|       a       |   b   |   c   |   p...a...d...d...i...n...g   |  s2
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

w элемент также будет означать, что есть 8 байтов в struct foo перед (анонимным) союзом, из которого вполне вероятно, что w занимает всего 4. Размер struct foo Поэтому 24 на этой машине. Это не особенно относится к обсуждению.

Теперь предположим, что у нас есть такой код:

struct foo d;
d.w = TYPE_B;
d.s2.a = 1234;
d.s2.b = 56;
d.s2.c = 78;

struct foo f;
f.s1 = d.s1;
f.w  = TYPE_B;

Теперь, согласно постановлению сноски 51, структура назначения f.s1 = d.s1; не нужно копировать биты заполнения. Я не знаю ни одного компилятора, который бы так себя вел, но в стандарте сказано, что компилятору не нужно копировать биты заполнения. Это означает, что значение f.s1 может быть:

+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| x | g...a...r...b...a...g...e |   r...u...b...b...i...s...h   |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Мусор состоит в том, что эти 7 байтов не нужно копировать (сноска 51 говорит, что это опция, хотя вряд ли она будет использоваться любым текущим компилятором). Мусор, потому что инициализация d никогда не устанавливайте значения в этих байтах; содержание этой части структуры не определено.

Если вы сейчас идете вперед и попробуйте лечить f как копия dВы можете быть немного удивлены, обнаружив, что только 1 байт из 8 соответствующих байтов f.s2 на самом деле инициализируется.

Я еще раз подчеркну: я не знаю ни одного компилятора, который бы делал это. Но вопрос помечен как "адвокат по языку", поэтому вопрос в том, "что означает языковой стандарт", и это мое толкование цитируемых разделов стандарта.

Да, ваш код будет работать, потому что при объединении компилятор будет использовать одинаковое пространство памяти для всех элементов.

Например, если: &f.mystruct = 100, тогда &f.mystruct2 = 100 и &f.mystruct3 = 100

Если mystruct самый большой, он будет работать постоянно.

Да, вы можете напрямую получить к ним доступ. Вы можете присвоить значение члену объединения и прочитать его через другого члена объединения. Результат будет детерминированным и правильным.

Другие вопросы по тегам