15

Is this code well-defined?

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

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

It accesses two different union members in one expression so I am wondering if it falls foul of the [class.union]/1 provisions about active member of a union.

The C++ Standard seems to underspecify which operations change the active member for builtin types, and what happens if an inactive member is read or written.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Wouldn't the second assignment make `u.i` the active member and `u.f` an inactive member? – R Sahu Aug 05 '15 at 05:13
  • 1
    No: if a function is declared to return a value, but doesn't, it's undefined behaviour (I jest, I assume you're talking about the `union` assignment). – Tas Aug 05 '15 at 05:26
  • 2
    I would say that, since value computation of `u.f` is sequenced before the side effect of the assignment, at no point in time is it ambiguous which member is the active one, and at no point in time is an inactive member being read from. The statement in question should behave the same as `auto temp = u.f; u.i = temp;` – Igor Tandetnik Aug 05 '15 at 05:31
  • 4
    @Tas `main` is special, see 3.6.1/5 – M.M Aug 05 '15 at 05:31

3 Answers3

6

The assignment operator (=) and the compound assignment operators all group right-to-left. [...] In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. [...]

[N4431 §5.18/1]

Under the premise that in your case the value computation of the left hand side (involving merely a member access, not a read) does not cause undefined behaviour, I'd apply the above as follows:

  1. The value computation of the right side reads u.f, which is the active member of the union. So everything is good.
  2. The assignment is executed. This involves writing the obtained result to u.i, which now changes the active member of the union.
Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • But the left hand side `u.i` is evaluated before the assignment, when the active member is still `u.f`. The wording about unions is unclear. – curiousguy Aug 16 '15 at 02:08
  • 1
    The wording is indeed unclear. But I think it's only undefined if one reads from an inactive member, and that the evaluation of the left hand side doesn't involve reading from `u.i`. – Daniel Jour Aug 16 '15 at 05:41
4

Inactive members can be written to. That is in fact the only way to make them active. The active member has no restrictions; it can be read from and written to.

You can take the address of an inactive member. This is a valid way to perform the write:

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.

In your example, the active member can only become u.i by writing 5 to it, which means that the value u.f must have been read.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • This presumes that the reading and the writing happen in a sequenced way; in particular, it assumes that the evaluation of the rhs is sequenced-before the writing of the lhs. I believe this is true: is it? – Yakk - Adam Nevraumont Aug 05 '15 at 11:18
  • Some people say that this violates strict aliasing. A crazy idea if you ask me, but they work on GCC... – curiousguy Aug 16 '15 at 02:10
  • @curiousguy: What precise part? GCC rightly complains about **reading** through another type. Logical, because that does not change the active member and is a type violation (reading a nonactive member). – MSalters Aug 18 '15 at 07:39
  • @MSalters The part where the doc says that writes to incompatibles types (like `int`/`float`) can be reordered. The part where the devs says you can't write a malloc-like in standard C/C++. – curiousguy Aug 18 '15 at 11:18
  • @curiousguy: The GCC folks are quite aggressive, but generally they're not stupid. C and C++ have the notion of an "active member" of a union, which is the last one written to. Reordering writes would break the active member. As for `malloc`, I'm entirely willing to believe that you can't write a **competitive** `malloc` implementation in Standard C. And strictly speaking you can't write any `malloc` replacement in Standard C anyway; where would it get its memory from? There's always an implied degree of non-portability. – MSalters Aug 18 '15 at 12:03
  • @MSalters I beg to disagree. "_Reordering writes would break the active member_" GCC said union is magic, so you can even break strict aliasing with union with GCC (explicitly allowed in doc), but only with a member access in the union. It isn't clear what GCC allows or not. – curiousguy Aug 18 '15 at 12:19
  • @MSalters "_where would it get its memory from?_" You can write a `my_malloc` using `malloc` instead of `mmap` for master allocation. But the GCC folks said that you can't, because `malloc` in C (and `operator new` in C++) are magic. You can put a `std::string` at the address where you have previously written an `int`, because the writes can be reordered! – curiousguy Aug 18 '15 at 12:22
  • @curiousguy: That's what I meant by `malloc`-replacement. If you implement it as a a `malloc` wrapper you get its magic, and `mmap` is of course not standard. As for reordering writes, remember that the Standard has a very simple sequential model for the execution of _conforming programs_. Reordering writes is allowed if it's not noticeable (as-if rule) or for programs which have Undefined Behavior. Not in any other case. `union`, being well-defined construct, in itself does not allow reordering until you violate inactive-member rules. – MSalters Aug 18 '15 at 14:46
  • Also see [this earlier answer](http://stackoverflow.com/a/11996970/15416) : UB is caused **specifically** by the lvalue-to-rvalue conversion of an uninitialized union variable. `*pf = 3.0;` - the write - does NOT require such a conversion. – MSalters Aug 18 '15 at 14:52
-2

By looking at [class.union]/1, from the considerations about standard layout unions containing standard layout unions sharing a common initial sequence it means that the following is ok

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
}

Here the common initial sequence is int x ;.

That's not the exactly the question but tells us that this kind of mix is OK.

marom
  • 5,064
  • 10
  • 14
  • 1
    `A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class.` The common initial sequence guarantee doesn't apply to `union`s – user657267 Aug 05 '15 at 07:35