Интерпретация битов в полях объединения как различных типов данных в C / C ++ - PullRequest
0 голосов
/ 02 июля 2018

Я пытаюсь получить доступ к битам объединения как к разным типам данных. Например:

    typedef union {
    uint64_t x;
    uint32_t y[2];
    }test;

    test testdata;
    testdata.x = 0xa;
    printf("uint64_t: %016lx\nuint32_t: %08x %08x\n",testdata.x,testdata.y[0],testdata.y[1]);
    printf("Addresses:\nuint64_t: %016lx\nuint32_t: %p %p\n",&testdata.x,&testdata.y[0],&testdata.y[1]);

Выход

uint64_t: 000000000000000a
uint32_t: 0000000a 00000000
Addresses:
uint64_t: 00007ffe09d594e0
uint32_t: 0x7ffe09d594e0 0x7ffe09d594e4

Начальный адрес, на который указывает y, совпадает с начальным адресом x. Поскольку оба поля используют одно и то же местоположение, не должны ли значения x быть 00000000 0000000a?

Почему этого не происходит? Как может происходить внутреннее преобразование в Союзе с разными полями разных типов данных?

Что нужно сделать, чтобы получить точные необработанные биты как uint32_t в том же порядке, что и в uint64_t, используя объединение?

Edit: Как упоминалось в комментариях, C ++ дает неопределенное поведение. Как это работает в C? Можем ли мы на самом деле это сделать?

1 Ответ

0 голосов
/ 02 июля 2018

Сначала я объясню, что происходит в вашей реализации.

Вы делаете печатание типа между значением uint64_t и массивом значений 2 uint32_t. В соответствии с результатом, ваша система имеет порядок байтов и с радостью принимает этот тип наказания, просто переосмысливая представления байтов. И байтовое представление 0x0a в порядке байтов uint64_t:

Byte number  0    1    2    3    4    5    6    7  
Value        0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00

Младший значащий байт в младшем порядке байтов имеет самый низкий адрес. Теперь очевидно, почему представление uint32_t[2] является { 0x0a, 0x00 }.

Но то, что вы делаете, разрешено только на языке C.

C язык:

C11 говорит как 6.5.2.3 Структура и члены профсоюза:

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

В примечании 95) прямо сказано:

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

Таким образом, даже если примечания не являются нормативными, их цель - прояснить, как должен интерпретироваться стандарт => ваш код действителен и имеет определенное поведение в системе с прямым порядком байтов, определяющей типы uint64_t и uint32_t.

C ++ язык:

C ++ более строг в этой части. В проекте n4659 для C ++ 17 в [basic.lval] написано:

8 Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из Для следующих типов поведение не определено: 56
(8.1) - динамический тип объекта,
(8.2) - cv-квалифицированная версия динамического типа объекта,
(8.3) - тип, подобный (как определено в 7.5) динамическому типу объекта,
(8.4) - тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
(8.5) - тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
(8.6) - агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих элементов или нестатический элементы данных (включая, рекурсивно, элемент или нестатический элемент данных субагрегата или содержавший союз),
(8.7) - тип, который является (возможно, квалифицированным по cv) типом базового класса динамического типа объекта,
(8.8) - тип char, unsigned char или std :: byte.

И в примечании 56 прямо говорится:

Цель этого списка - указать те обстоятельства, при которых объект может или не может иметь псевдоним.

Поскольку punning никогда не упоминается в стандарте C ++, а часть struct / union не содержит эквивалента повторной интерпретации C, это означает, что чтение в C ++ значение члена, который не является последним записанным, вызывает неопределенное поведение.


Конечно, обычная реализация компилятора компилирует и C, и C ++, и большинство из них принимает идиому C даже в исходном коде C ++ по той же причине, по которой компилятор gcc C ++ с радостью принимает VLA в исходных файлах C ++. В конце концов, неопределенное поведение включает ожидаемые результаты ... Но вы не должны полагаться на это для переносимого кода.

...