4

这个答案的评论中,据说使用如下的联合将整数分成它们的字节是未定义的行为。在那个地方给出的代码与此相似但不完全相同,如果我更改了代码的未定义行为相关方面,请注明。

union addr {
 uint8_t addr8[4];
 uint32_t addr32;
};

到目前为止,我认为这将是一种很好的方法来做类似的事情addr = {127, 0, 0, 1};并获得相应 uint32_t的回报。(我承认这可能会根据我的系统的字节顺序产生不同的结果。但是问题仍然存在。)

这是未定义的行为吗?如果是这样,为什么?(我不知道C++ 中的 UB 是什么意思是访问非活动的工会成员。


C99

  • 在这一点上,C99 显然非常接近 C++03。

C++03

  • 在一个联合中,任何时候最多有一个数据成员处于活动状态,即任何时候最多可以有一个数据成员的值存储在一个联合中。C++03,第 9.5 节 (1),第 162 页

然而

  • 如果一个 POD 联合包含多个共享一个公共初始序列的 POD 结构 [...],则允许​​检查任何 POD 结构成员的公共初始序列,同上。
  • 如果两个 POD-struct [...] 类型具有相同数量的非静态数据成员,则它们是布局兼容的,并且相应的非静态数据成员(按顺序)具有布局兼容类型C++03,第 9.2 (14) 节,第 157 页
  • 如果两个类型 T1 和 T2 是同一类型,则 T1 和 T2 是布局兼容类型。C++03,第 3.9 (11) 节,第 53 页

结论

  • asuint8_t[4]uint32_t不是同一类型(我猜,一个严格的别名)(加上两者都不是 POD-structs/union)上面确实是 UB?

C++11

  • 请注意,聚合类型不包括联合类型,因为具有联合类型的对象一次只能包含一个成员。C++11,脚注 46,第 42 页
4

4 回答 4

10

我不知道 C++ 中的 UB 是什么意思是访问不活动的工会成员。

基本上,这意味着您可以从联合中读取而不调用未定义行为的唯一成员是最后写入的成员。换句话说,如果您写入addr32,您只能从 读取addr32addr8反之亦然。

此处也提供了一个示例。

编辑:由于是否是 UB 已经进行了很多讨论,请考虑以下(完全有效的)C++11 示例;

union olle {
    std::string str;
    std::wstring wstr;
};

在这里你肯定可以看到激活str和读取wstr可能是有问题的。您可以将其视为一个极端示例,因为您甚至必须通过执行新位置来激活成员,但规范实际上涵盖了这种情况,没有提到它在其他方面被视为有关活动成员的特殊情况。

于 2012-04-22T20:46:28.447 回答
7

[编辑:阅读下面我编辑的部分,因为我现在不确定这是否是未定义的行为;但是,在我进一步确认之前,我的大部分答案都会保持不变] 是的,这是未定义的行为。C++ 标准第 9.5.1 节规定:

在一个union中,任何时候最多只能有一个非静态数据成员处于活动状态,即任何时候最多可以有一个非静态数据成员的值存储在一个union中。[注意:为了简化联合的使用,我们做了一个特别的保证:如果一个标准布局联合包含几个共享一个公共初始序列(9.2)的标准布局结构,并且如果这个标准布局联合类型的对象包含一个标准布局结构,允许检查任何标准布局结构成员的公共初始序列;见 9.2。——尾注]

这意味着也只能有效地读取最近写入的成员(从其他成员读取在技术上是未定义的行为)。任何时候都只能有一名工会成员处于活动状态。不是两个。

你可能会问为什么?考虑你的例子。C++ 不要求addr32. 它可以是大端、小端或中端。如果您写入addr8,然后从 读取addr32,C++ 不能保证您会得到正确的值,因为在这种情况下是字节序。一台计算机,它可能是一个值,而在另一台计算机上,它可能是一个不同的值。因此,这样做(即写入一个成员并读取另一个成员)是未定义的行为。

编辑:对于那些想知道“活动”是什么意思的人,关于工会的 MSDN 文档指出:

联合的活动成员是最近设置值的成员,并且只有该成员具有有效值。

编辑 编辑:我一直认为这样做的行为是不确定的,但现在我不太确定在 R. Martinho Fernandes 的评论和回答以及重新阅读 MSDN 的引用之后。该值当然是未指定/未定义的,但现在我不太确定行为是否是(未定义的值意味着您可能会得到不同的结果;未定义的行为意味着您的系统可能会崩溃,两者是不同的东西)。我将进一步考虑这一点,并与我认识的其他人交谈,看看我是否能找到更明确的答案。

但是,我确实认为可以肯定地说,通常读取联合中的非活动成员可能是未定义的行为(当然,标准中的特殊说明除外),但我不知道它是否总是如此(即除了我引用的 C++ 标准部分的特别说明之外,可能还有一些例外情况)。

于 2012-04-22T20:52:12.643 回答
5

坦率地说,我在标准中找不到任何提及这样做是未定义的行为。该标准确实为工会定义了“活动成员”的概念,但除了解释如何更改活动成员(§9.5p4)和定义常量表达式(§5.9p2)之外,它似乎没有将这个想法用于任何其他事情)。具体来说,它似乎没有明确提及访问活动成员或非活动成员的有效性。

据我所见,类似以下内容可能会导致严格的别名违规,这是未定义的行为:

union example0 {
    short some_other_view[sizeof(double)/sizeof(short)];
    double value;
};

由于工会的一些特殊规则,这不会导致严格的别名违规。如果您使用无法别名的类型访问相同的内存位置,就会发生这种情况,即“正常”的严格别名违规。

但是,由于在别名规则方面存在例外情况char,因此以下内容不会导致相同类型的违规:

union example1 {
    char byte_view[sizeof(double)];
    double value;
};

据我所见,标准中没有任何内容使以下代码具有未定义的行为:

example1 e;
e.value = 10.0;
std::out << e.byte_view[0];
于 2012-04-22T21:21:46.830 回答
5

基本上是因为在 C++ 中,您只能访问联合的活动成员。

这意味着如果你设置addr8了,那么你应该只访问那个,直到你设置addr32,这样你就可以访问它等等。将一个成员设置为从另一个成员访问数据会导致未定义的行为。

当您设置一个成员时,它被认为是活动的,并且在另一个成员成为活动成员之前它一直如此

于 2012-04-22T20:46:46.987 回答