Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is cast a pointer points-to-class to it's first member illegal?

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.

like image 567
maidamai Avatar asked Dec 19 '22 00:12

maidamai


2 Answers

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;

like image 154
Passer By Avatar answered Jan 16 '23 13:01

Passer By


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.

like image 31
Zebrafish Avatar answered Jan 16 '23 12:01

Zebrafish