使用联合在具有共同初始序列的两个结构之间进行转换是否合法且定义良好的行为(参见示例)?

Cod*_*der 16 c c++ c99 c89 unions

我有一个公开面向结构A和内部结构B的API,需要能够将结构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;
}
Run Code Online (Sandbox Code Playgroud)


UPDATE

我简化了示例并编写了两个不同的应用程序.一个基于memcpy,另一个基于union.


联盟:

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;
}
Run Code Online (Sandbox Code Playgroud)


的memcpy:

#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;
}
Run Code Online (Sandbox Code Playgroud)



Profiled Assembly [DEBUG](Xcode 6.4,默认C++编译器):

以下是调试模式的程序集中的相关差异.当我分析发布版本时,程序集没有任何区别.


联盟:

movq     %rcx, -48(%rbp)
Run Code Online (Sandbox Code Playgroud)


的memcpy:

movq    -40(%rbp), %rsi
movq    %rsi, -56(%rbp)
movl    -32(%rbp), %edi
movl    %edi, -48(%rbp)
Run Code Online (Sandbox Code Playgroud)



警告:

基于union的示例代码会生成关于变量'a'未使用的警告.由于配置文件程序集来自调试,我不知道是否有任何影响.

eca*_*mur 11

这很好,因为您访问的成员是公共初始序列的元素.

C11(6.5.2.3结构和联合成员 ; 语义):

[...]如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查其中任何一个的公共初始部分.完整的工会类型的声明是可见的.如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列.

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

如果POD-union包含两个或多个共享公共初始序列的POD结构,并且如果POD-union对象当前包含这些POD结构中的一个,则允许检查它们中的任何一个的公共初始部分.如果对应的成员具有一个或多个初始成员的序列的布局兼容类型(并且对于位字段,具有相同的宽度),则两个POD结构共享共同的初始序列.

这两个标准的其他版本有相似的语言; 从C++ 11开始,使用的术语是标准布局而不是POD.


我认为可能会产生混淆,因为C允许通过C++不会出现的并集来进行类型惩罚(别名为不同类型的成员); 这是确保您必须使用的C/C++兼容性的主要案例memcpy.但在您的情况下,您访问的元素具有相同的类型,并且前面是兼容类型的成员,因此类型 - 惩罚规则不相关.


n. *_* m. 5

它在C和C++中都是合法的

例如,在C99(6.5.2.3/5)和C11(6.5.2.3/6)中:

为了简化联合的使用,我们做了一个特殊的保证:如果一个联合包含几个共享一个共同初始序列的结构(见下文),并且如果联合对象当前包含这些结构中的一个,则允许检查公共其中任何一个的初始部分都可以看到完整类型的联合声明.如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列.

C++ 11和C++ 14中存在类似的规定(不同的措辞,含义相同).