联合成员之间的赋值

15

这段代码是否已经定义良好?

int main()    
{    
    union     
    {    
        int i;    
        float f;    
    } u;    

    u.f = 5.0;    
    u.i = u.f;       // ?????
}    

它在一个表达式中访问了两个不同的联合成员,因此我想知道它是否违反了[class.union]/1有关联合体“活动成员”的规定。

C++标准似乎未明确规定哪些操作会更改内置类型的活动成员,并且如果读取或写入非活动成员会发生什么。


第二个赋值语句会使 u.i 成为活跃成员,而 u.f 成为非活跃成员吗? - R Sahu
1
如果一个函数声明需要返回一个值,但是没有返回,那么它的行为是未定义的(开玩笑的,我假设你在谈论union赋值)。 - Tas
2
我认为,由于u.f的值计算在赋值的副作用之前被排序,因此在任何时候都不会存在哪个成员是活动成员不明确的情况,并且在任何时候都不会从非活动成员中读取。所讨论的语句应该与auto temp = u.f; u.i = temp;的行为相同。 - Igor Tandetnik
4
@Tas main 是特殊的,参见 3.6.1/5。 (翻译:@Tas main is special, see 3.6.1/5) - M.M
3个回答

6
赋值运算符(=)和复合赋值运算符都是从右往左组合的。在所有情况下,赋值会在右侧和左侧操作数的值计算之后、赋值表达式的值计算之前进行顺序化。在您的情况下,如果左侧的值计算(仅涉及成员访问,而不是读取)不会导致未定义行为,则可以按照以上方法执行以下操作:
1. 右侧的值计算读取了union的活动成员u.f,所以一切正常。
2. 执行赋值操作,将获得的结果写入u.i,这样会改变union的活动成员。

但是左侧的 u.i 在赋值之前被计算,此时活动成员仍然是 u.f。有关联合体的措辞不够清晰。 - curiousguy
1
措辞确实不清楚。但我认为只有在从非活动成员读取时才是未定义的,而左侧的评估不涉及从u.i读取。 - Daniel Jour

4

非活动成员可以被编写。事实上,这是激活它们的唯一方式。活动成员没有限制;它可以被读取和编写。

可以获取非活动成员的地址。这是执行写入的有效方法:

union {
  int i;
  float f;
} u;
float* pf = &u.f; // Does NOT change the active member.
u.i = 3;
*pf = 3.0; // Changes the active member.

在你的例子中,活动成员只能通过向 u.i 写入 5 来成为活动状态,这意味着必须已经读取了值 u.f

@curiousguy:具体是哪一部分?GCC正确地抱怨了通过另一个类型进行读取。这是合理的,因为它不会更改活动成员并且是一种类型违规(读取非活动成员)。 - MSalters
@MSalters文档中提到的写入不兼容类型(如int/float)可以被重新排序的部分。开发人员表示,在标准C/C++中无法编写类似于malloc的内容。 - curiousguy
@MSalters 我不同意。 “重新排序写入将破坏活动成员” GCC说联合是魔法,所以你甚至可以使用GCC打破严格别名与联合(在文档中明确允许),但只能通过联合中的成员访问来实现。不清楚GCC允许什么或不允许什么。 - curiousguy
@MSalters "它从哪里获取内存?" 您可以编写一个使用malloc而不是mmap进行主分配的my_malloc。但是GCC的人说您不能这样做,因为C中的malloc(以及C ++中的operator new)是神奇的。您可以将std :: string放在先前写入int的地址处,因为写入可以重新排序! - curiousguy
还可以参考这个早期的答案:UB是由未初始化的联合变量的左值到右值转换特别引起的。*pf = 3.0; - 写操作 - 不需要进行这样的转换。 - MSalters
显示剩余4条评论

-2
通过查看[class.union] / 1,从包含共享共同初始序列的标准布局联合体的标准布局联合体的考虑可以得出以下结论是可以的。
int main()
{
   union 
   {
      union 
      {
          int x ;
          float y ;
      } v ;
      union 
      {
          int x ;
          double z ;
      } w ;      
   } u ;

   u.v.x = 2 ;
   int n = u.w.x ; // n contains 2
}

这里的常见初始序列是int x;

这并不是确切的问题,但告诉我们这种混合是可以的。


1
标准布局结构体是使用class-key struct或class-key class定义的标准布局类。共同初始序列保证不适用于union。 - user657267

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接