【发布时间】:2017-02-07 09:08:23
【问题描述】:
重要的澄清:一些评论者似乎认为我是从工会复制的。仔细查看memcpy,它从一个普通的旧uint32_t 的地址复制而来,该地址不包含在联合中。另外,我正在(通过memcpy)复制到工会的特定成员(u.a16 或&u.x_in_a_union,而不是直接复制到整个工会本身(&u)
C++ 对联合非常严格 - 只有当该成员是最后写入的成员时,您才应该从该成员中读取:
9.5 联合[class.union] [[c++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's the active member?
紧接在此上方的代码块可能是 cmets 和答案中的主要问题。事后看来,我不需要在这个问题中混合类型!基本上,假设类型相同,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。 这是对我们为什么关心这个问题的恰当总结吗?
【问题讨论】:
-
由您来跟踪“活跃”成员是什么。编译器不会为你做这件事。
-
您的代码违反了语言律师规则,无论如何都不可移植;例如,它将在大端或小端机器上产生不同的输出,
-
什么是“语言律师”“规则”,@Lorehead
-
到目前为止,有很多天真的 cmets 和答案。天真的答案当然是“当然联合是不变的:它是一样的,init”。这个好问题出奇地深。由于可能读取未初始化的内存,我并不完全相信您甚至可以
memcpy联合。请参阅stackoverflow.com/questions/33393569/…,尽管那是在 C 标签上。 -
我认为因为标准对这种行为非常模糊,所以没有“正确”的答案。每个编译器都可能允许它,结果可能相同,但它不“符合标准”。
标签: c++ c++11 language-lawyer unions object-lifetime