5

I see some strange code in our project like follows.I test it and get the right answer.But I think it is illegal,Can anyone explain this to me?

class Member
{
public:
    Member():
        a(0),b(1)
    {}

    int a;
    int b;
};

// contains `Member` as its first member
class Container
{
public:
    Container():
        c(0),d(0)
    {}

    Member getMemb(){return fooObject;}

    Member fooObject;
    int c;
    int d;
};

and how we use it:

int main()
{
    auto ctain = new Container;
    auto meb = (Member *)ctain; // here! I think this is illegal

    cout << "a is " <<  meb->a << ", b is" << meb->b << endl;

    return 0;
}

but I get the right answer, a is 0 and b is 1.Is this just a coincidence?I also noted that if fooObject is not the first member, I will get a wrong answser.

maidamai
  • 712
  • 9
  • 26
  • Based on [this](https://stackoverflow.com/a/4167575/1977152) answer. I think it's better to avoid casting if it's possible(more so, the C-style casting you're using). The answer also mentions the different C++ casts which could be more useful to you. I think if you want your project to not break down in future if want to make any changes, you need to steer clear from such casts operations. – Zaid Khan Mar 17 '18 at 08:18
  • It's not illegal but does assume that the class has its members laid out in memory that way. You will have to keep the member variable where it is, and not add any virtual methods. Better to add an access method and get rid of all the C-style casts. – Ian4264 Mar 17 '18 at 08:19

3 Answers3

8

The snippet is legal. The C style cast (Member*) here is effectively a reinterpret_cast. From [basic.compound]

Two objects a and b are pointer-interconvertible if:

  • they are the same object, or

  • one is a union object and the other is a non-static data member of that object, or

  • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or [...]

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast.

Special care should be taken to make sure it is indeed a standard layout type, possibly with a static_assert(std::is_standard_layout_v<Container>)

On the other hand, you could sidestep this entire fiasco if you just wrote auto meb = &ctain.fooObject;

Community
  • 1
  • 1
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • 1
    Even if it's interconvertible which you've shown it is, does it break the strict aliasing rule? – Zebrafish Mar 17 '18 at 08:59
  • 1
    @Zebrafish Strict aliasing means to not access an object with a type not the same as the object, so no. The issue with casting pointers is generally the pointer doesn't point to the correct object even though their value is the same, which is what pointer interconvertibility is about. – Passer By Mar 17 '18 at 09:05
3

It's not exactly coincidence, it happens that your fooObject is the first member of your Container class, so the beginning of it will rest at the same starting address as the Container object. If you do:

size_t s = offsetof(Container, Container::fooObject);

It will tell that your fooObject offset will be 0, which start where your Container object start in terms of memory, so when you cast to a Member pointer it's pointing to the correct address. But for instance in other cases you would be in big trouble for sure:

class Container
{
public:
    Container() : c(0),d(0) {}

    Member getMemb(){return fooObject;}

    int c; // fooObject isn't first member
    Member fooObject;
    int d;
};

Or was a virtual class, because virtual classes store an pointer for lookup into a table.

class Container
    {
    public:
        Container() : c(0),d(0) {}
        virtual ~Container() {} // Container is virtual, and has a vtable pointer
                                // Meaning fooObject's offset into this class
                                // most likely isn't 0
        Member getMemb(){return fooObject;}

        Member fooObject;
        int c;
        int d;
    };

Someone else will have to tell you whether this cast is legal even in your example, because I'm not sure.

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
1

C++ Standard in part 12.2 Class members:

If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any).

Both your classes have standard-layout. So, observed behaviour agrees with Standard.

But the cast auto meb = (Member *)ctain; breaks strict aliasing rule.

Yola
  • 18,496
  • 11
  • 65
  • 106
  • Would reinterpret_cast be the same? I'm always nervous about these sorts of casts because people warn about them so much. For example I keep hearing that a cast to another type isn't guaranteed to point to the same address, but that the only guarantee you have is that when casting back to its original type you have the exact same pointer to the exact same address. – Zebrafish Mar 17 '18 at 08:23
  • Also, if it breaks strict aliasing rule does that make it illegal? Standard conformant but illegal or both illegal and not standard conformant? – Zebrafish Mar 17 '18 at 08:28
  • You need pointer interconvertibility to guarantee the `reinterpret_cast` legal, and actually, it is interconvertible – Passer By Mar 17 '18 at 08:29
  • @Zebrafish about `reinterpret_cast` - yes. It doesn't make it illegal, you can write code this way it will work ok, just you make life harder for your compiler and it can bite you. – Yola Mar 17 '18 at 08:29