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

У меня есть API с открытой структурой A и внутренней структурой B и должен иметь возможность преобразовать структуру B в структуру A. Является ли следующий код легальным и четко определенным поведением в C99 (и VS 2010/C89) и С++ 03/С++ 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;
}


UPDATE

Я упростил пример и написал два разных приложения. Один основан на 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, компилятор С++ по умолчанию):

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


Союз:

movq     %rcx, -48(%rbp)


тетср:

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



Оговорка:

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

Ответ 1

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

C11 (6.5.2.3 Элементы структуры и объединения; Семантика):

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

С++ 03 ([класс .mem]/16):

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

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


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

Ответ 2

Это законно как в C, так и в С++

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

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

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