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

时间:2015-07-22 03:36:32

标签: c++ c c99 unions c89

我有一个面向公众的结构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;
}


更新

我简化了示例并编写了两个不同的应用程序。一个基于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;
}


的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;
}



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

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


联:

movq     %rcx, -48(%rbp)


的memcpy:

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



警告:

基于union的示例代码会生成关于变量&#39; a&#39;没用。由于配置文件是来自调试,我不知道是否有任何影响。

2 个答案:

答案 0 :(得分:11)

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

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

  

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

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

  

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

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

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

答案 1 :(得分:5)

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

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

  

为了简化工会的使用,我们提出了一项特殊保证:如果工会包含   几个结构共享一个共同的初始序列(见下文),如果是联盟   对象当前包含这些结构中的一个,允许检查公共结构   任何一个声明完整类型的联盟的任何地方的初始部分   可见。如果相应的成员有两个结构共享一个共同的初始序列   对于一个或多个序列的兼容类型(以及对于位字段,相同的宽度)   初始成员。

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