Важное пояснение: некоторые комментаторы, похоже, думают, что я копирую из профсоюза. Посмотрите внимательно на memcpy
, он копирует с адреса простого старого uint32_t
, который не содержится в объединении. Кроме того, я копирую (через memcpy
) конкретный член объединения (u.a16
или &u.x_in_a_union
, а не непосредственно ко всему самому объединению (&u
)
С++ довольно жестко относится к объединениям - вы должны читать от члена только в том случае, если это был последний элемент, который был написан для:
9.5. Unions [class.union] [[С++ 11]] В объединении не более одного нестатического элемента данных могут быть активны в любое время, то есть значение не более одного из нестатические элементы данных могут быть сохранены в объединении в любое время.
(Конечно, компилятор не отслеживает, какой член активен. Это зависит от разработчика, чтобы убедиться, что они сами отслеживают это)
Обновление: этот следующий блок кода является основным вопросом, непосредственно отражающим текст в заголовке вопроса. Если этот код в порядке, у меня есть продолжение относительно других типов, но теперь я понимаю, что этот первый блок кода интересен сам.
#include <cstdint>
uint32_t x = 0x12345678;
union {
double whatever;
uint32_t x_in_a_union; // same type as x
} u;
u.whatever = 3.14;
u.x_in_a_union = x; // surely this is OK, despite involving the inactive member?
std::cout << u.x_in_a_union;
u.whatever = 3.14; // make the double 'active' again
memcpy(&u.x_in_a_union, &x); // same types, so should be OK?
std::cout << u.x_in_a_union; // OK here? What the active member?
Блок кода непосредственно над этим, вероятно, является главной проблемой в комментариях и ответах. Оглядываясь назад, мне не нужно было смешивать типы в этом вопросе! В принципе, u.a = b
совпадает с memcpy(&u.a,&b, sizeof(b))
, если типы идентичны?
Во-первых, относительно простой memcpy
, позволяющий нам читать uint32_t
как массив uint16_t
:
#include <cstdint> # to ensure we have standard versions of these two types
uint32_t x = 0x12345678;
uint16_t a16[2];
static_assert(sizeof(x) == sizeof(a16), "");
std:: memcpy(a16, &x, sizeof(x));
Точное поведение зависит от контентоспособности вашей платформы, и вы должны остерегаться ловушек и т.д. Но в целом это согласуется с этим (я думаю, что отзывы заслуживают!), Что, с осторожностью, чтобы избежать проблемных значений, приведенный выше код может быть абсолютно стандартным жалобой в правильном контексте на правильной платформе.
(Если у вас есть проблема с указанным выше кодом, пожалуйста, прокомментируйте или отредактируйте вопрос соответственно. Я хочу быть уверенным, что у нас есть не противоречивая версия выше, прежде чем перейти к "интересному" коду ниже.)
Если и только если оба блока кода выше не являются UB, я хотел бы объединить их следующим образом:
uint32_t x = 0x12345678;
union {
double whatever;
uint16_t a16[2];
} u;
u.whatever = 3.14; // sets the 'active' member
static_assert(sizeof(u.a16) == sizeof(x)); //any other checks I should do?
std:: memcpy(u.a16, &x, sizeof(x));
// what is the 'active member' of u now, after the memcpy?
cout << u.a16[0] << ' ' << u.a16[1] << endl; // i.e. is this OK?
Какой член объединения, u.whatever
или u.a16
, является "активным членом"?
Наконец, моя собственная догадка заключается в том, что причина, по которой мы в этом заботимся, на практике заключается в том, что оптимизирующий компилятор может не заметить, что произошел
memcpy
и, следовательно, сделать ложные предположения (но допустимые допущения по стандарту) о том, какой член активен и какие типы данных являются "активными", что приводит к ошибкам в области псевдонимов. Компилятор может изменить порядок memcpy
странными способами. Является ли это подходящим резюме того, почему мы заботимся об этом?