3

Consider the following code:

#include <cstdint>

struct B {
    uint32_t c() {
        uint32_t * value = reinterpret_cast<uint32_t *>(this);
        return * value;
    }
};

struct A {
    union {
        B b1;
        B b2;

        uint32_t a { 10 };
    };
};

int test() {
    A a;
    return a.b1.c();
}

Here test() returns 10 because all A is a union-like struct. My question is, assuming A satisfies StandardLayoutType concept, is casting this inside B::c to get pointer to a A::a undefined behavior?

1 Answers1

4

This is undefined behavior. As an overview the union contains either a uint32_t or a B.

  • If it's a B then the cast is illegal (since it's not a uint32_t you mustn't cast to it).
  • If it is a uint32_t then calling the .c() member is illegal since you can't access the b1 member (isn't the active union member).

In this case (thanks to @StoryTeller's comment) the active union member is a (the uint32_t) since it's the only one with default initialization, thus calling a.b1.c() is UB.

Motti
  • 110,860
  • 49
  • 189
  • 262
  • It is initialized on account of that default member initializer. Not that it helps much with all the other UB. – StoryTeller - Unslander Monica Jan 16 '18 at 09:24
  • @StoryTeller, I'm not up to date on the rules regarding union members with constructors, does this mean that both `b1` and `b2`'s constructors are run? In this case, which is the active member? I know that non-POD members to unions have been added to C++ but I'm not sure about the details. – Motti Jan 16 '18 at 09:33
  • @StoryTeller, according to [this answer](https://stackoverflow.com/a/19764531/3848) I'm not sure you're right. – Motti Jan 16 '18 at 09:38
  • 1
    No, they aren't run. `A a;` will attempt to do default initialization. Colloquially, since there is only one member with a default member initializer, that's the "default active member". If the default member initializer is removed, then `b1` will be initialized as thought by an empty pair of braces (`{}`), since it's of a trivial type. And that answer is irrelevant here. All the members of the union are of trivial types. The default constructor is not defined deleted (if it was, the program in the OP would not even build, let alone have UB). – StoryTeller - Unslander Monica Jan 16 '18 at 09:41
  • Can I get rid of UB if I remove default constructor, assuming that `B` will not have any data members? – Vladislav Ivanov Jan 16 '18 at 10:20
  • Actualy the cast is perfectly legal. The only illegal things is the second class member access in a.b.c(). – Oliv Jan 16 '18 at 10:22
  • 2
    @JohnDoe No you can not, because in the expression `(a.b).c()` `a.b` is evaluated, so `b` is accessed (its value is read) out of its lifetime (as long as we consider that evaluate means access see the footnote in the standard) see [\[expr.ref\]](https://timsong-cpp.github.io/cppwp/n4618/expr.ref#1) – Oliv Jan 16 '18 at 10:26