Является ли законным и четко определенным поведение использовать объединение для преобразования между двумя структурами с общей начальной последовательностью (см. Пример)?

У меня есть API с общедоступной структурой A и внутренней структурой B, и мне нужно иметь возможность преобразовать структуру B в структуру A. Является ли следующий код законным и четко определенное поведение в C99 (и VS 2010 / C89) и C ++ 03 / C ++ 11? Если это так, пожалуйста, объясните, что делает его четко определенным. Если это не так, что является наиболее эффективным и кроссплатформенным средством для преобразования между двумя структурами?

struct A {
uint32_t x;
uint32_t y;
uint32_t z;
};

struct B {
uint32_t x;
uint32_t y;
uint32_t z;
uint64_t c;
};

union U {
struct A a;
struct B b;
};

int main(int argc, char* argv[]) {
U u;
u.b.x = 1;
u.b.y = 2;
u.b.z = 3;
u.b.c = 64;

/* Is it legal and well defined behavior when accessing the non-write member of a union in this case? */
DoSomething(u.a.x, u.a.y, u.a.z);

return 0;
}

ОБНОВИТЬ

Я упростил пример и написал два разных приложения. Один основан на memcpy, а другой использует объединение.

Союз:

struct A {
int x;
int y;
int z;
};

struct B {
int x;
int y;
int z;
long c;
};

union U {
struct A a;
struct B b;
};

int main(int argc, char* argv[]) {
U u;
u.b.x = 1;
u.b.y = 2;
u.b.z = 3;
u.b.c = 64;
const A* a = &u.a;
return 0;
}

тетср:

#include <string.h>

struct A {
int x;
int y;
int z;
};

struct B {
int x;
int y;
int z;
long c;
};

int main(int argc, char* argv[]) {
B b;
b.x = 1;
b.y = 2;
b.z = 3;
b.c = 64;
A a;
memcpy(&a, &b, sizeof(a));
return 0;
}

Профилированная сборка [DEBUG] (Xcode 6.4, компилятор C ++ по умолчанию):

Вот соответствующая разница в сборке для режима отладки. Когда я профилировал сборку релиза, в сборке не было никакой разницы.

Союз:

movq     %rcx, -48(%rbp)

тетср:

movq    -40(%rbp), %rsi
movq    %rsi, -56(%rbp)
movl    -32(%rbp), %edi
movl    %edi, -48(%rbp)

Предостережение:

Пример кода, основанного на union, выдает предупреждение о том, что переменная ‘a’ не используется. Поскольку профилированная сборка является отладочной, я не знаю, есть ли какое-либо влияние.

16

Решение

Это нормально, потому что члены, к которым вы обращаетесь, являются элементами общая начальная последовательность.

C11 (6.5.2.3 Структура и члены профсоюза; Семантика):

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

C ++ 03 ([Class.mem] / 16):

Если POD-объединение содержит две или более POD-структуры, которые имеют общую начальную последовательность, и если объект POD-union в настоящее время содержит одну из этих POD-структур, разрешается проверять общую начальную часть любой из них. Две POD-структуры совместно используют общую начальную последовательность, если соответствующие элементы имеют совместимые с макетом типы (и, для битовых полей, одинаковой ширины) для последовательности из одного или нескольких начальных элементов.

Другие версии двух стандартов имеют похожий язык; начиная с C ++ 11 используемая терминология стандартный макет скорее, чем POD.


Я думаю, что путаница, возможно, возникла, потому что С позволяет типа каламбурная (псевдоним члена другого типа) через объединение, где C ++ не делает; это основной случай, когда для обеспечения совместимости C / C ++ вам придется использовать memcpy, Но в вашем случае элементы, к которым вы обращаетесь, имеют так же type и ему предшествуют члены совместимых типов, поэтому правило определения типов не имеет значения.

11

Другие решения

Это допустимо как в C, так и в C ++.

Например, в C99 (6.5.2.3/5) и C11 (6.5.2.3/6):

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

Аналогичные положения существуют в C ++ 11 и C ++ 14 (разные формулировки, то же значение).

5