strange multi-inheritance problem

Discussion in 'C++' started by Patricia, Sep 20, 2005.

  1. Patricia

    Patricia Guest

    First, I know the following code is bad, but it's from a library I have
    to use, and I can't change it.

    class A {
    // some primitive members
    };

    class B {
    // some primitive members
    };

    class C : public A, B {
    // no members
    };

    main() {
    // I know sizeof(A) = 16, sizeof(B) = 60
    C * pc = (C*) new char[sizeof(A) + sizeof(B)];
    B * pb = (B*) pc;

    char * p1 = (char *) pc;
    char * p2 = (char *) pb;

    cout << "offset : " << (p2 - p1) << endl;
    }

    What surprised me is that the offset is 20 instead of 16. Do you know
    the possible reason?

    Regards.
    Patricia, Sep 20, 2005
    #1
    1. Advertising

  2. Patricia

    Patricia Guest

    Also it turns out that sizeof(C) = 80.
    Patricia, Sep 20, 2005
    #2
    1. Advertising

  3. "Patricia" <> wrote in message
    news:...
    >
    > First, I know the following code is bad, but it's from a library I have
    > to use, and I can't change it.
    >
    > class A {
    > // some primitive members
    > };
    >
    > class B {
    > // some primitive members
    > };
    >
    > class C : public A, B {
    > // no members
    > };
    >
    > main() {


    The return type of main must be explicitly specified as int:

    int main() {

    > // I know sizeof(A) = 16, sizeof(B) = 60


    Do you also know sizeof(C)?

    > C * pc = (C*) new char[sizeof(A) + sizeof(B)];


    That is undefined behavior. You cannot assume that sizeof(C) will be equal
    to sizeof(A) and sizeof(B). Nor can you assume that there is a C object
    there. Those are just a bunch of uninitialized characters. You cannot assume
    that they can be readily used as a C.

    > B * pb = (B*) pc;


    Actually, C inherits from B privately. You can't assume that the C-style
    cast above will give you access to the B part a C. If you intended to
    inherit publicly from C, then the cast is not needed:

    B * pb = pc;

    > char * p1 = (char *) pc;
    > char * p2 = (char *) pb;
    >
    > cout << "offset : " << (p2 - p1) << endl;
    > }
    >
    > What surprised me is that the offset is 20 instead of 16. Do you know
    > the possible reason?


    The reason is that the program's behavior is undefined.

    As a side note, you should abandon using the C-style casts. Use one of C++'s
    casting operators to make it explicit what you're saying, and to get help
    from the compiler when the casting is illegal.

    Ali
    =?iso-8859-1?Q?Ali_=C7ehreli?=, Sep 20, 2005
    #3
  4. "Patricia" <> wrote in message
    news:...
    >
    > First, I know the following code is bad, but it's from a library I have
    > to use, and I can't change it.
    >
    > class A {
    > // some primitive members
    > };
    >
    > class B {
    > // some primitive members
    > };
    >
    > class C : public A, B {
    > // no members
    > };
    >
    > main() {
    > // I know sizeof(A) = 16, sizeof(B) = 60
    > C * pc = (C*) new char[sizeof(A) + sizeof(B)];
    > B * pb = (B*) pc;
    >
    > char * p1 = (char *) pc;
    > char * p2 = (char *) pb;
    >
    > cout << "offset : " << (p2 - p1) << endl;
    > }
    >
    > What surprised me is that the offset is 20 instead of 16. Do you know
    > the possible reason?


    The following is a better way of doing what you are trying to do:

    #include <iostream>
    #include <memory>

    class A {
    // some primitive members
    int a[4];
    };

    class B {
    // some primitive members
    int a[15];
    };

    class C : public A, public B {
    // no members
    };

    int main() {
    std::cout << " sizeof(A): " << sizeof(A)
    << " sizeof(B): " << sizeof(B)
    << " sizeof(C): " << sizeof(C)
    << '\n';

    // Allocate a buffer
    char * buffer = new char[sizeof(C)];

    // Use placement new to construct an object on that buffer
    C * pc = new (reinterpret_cast<void *>(buffer)) C();

    // A valid up-cast
    B * pb = pc;

    char * p1 = reinterpret_cast<char *>(pc);
    char * p2 = reinterpret_cast<char *>(pb);

    // WARNING: Undefined behavior below! We cannot subtract two
    // pointers unless they point to objects of the same
    // array. Nevertheless, the results may be useful on a given system.
    std::cout << "offset : " << (p2 - p1) << endl;

    // Destroy the C object manually and release the buffer
    pc->~C();
    delete buffer;
    }

    Ali
    =?iso-8859-1?Q?Ali_=C7ehreli?=, Sep 20, 2005
    #4
  5. "Ali Çehreli" <> wrote in message
    news:dgplnh$hap$...

    > // Allocate a buffer
    > char * buffer = new char[sizeof(C)];
    >
    > // Use placement new to construct an object on that buffer
    > C * pc = new (reinterpret_cast<void *>(buffer)) C();


    [...]

    > // Destroy the C object manually and release the buffer
    > pc->~C();
    > delete buffer;
    > }


    And that would be a bug of course. :) Should be:

    delete[] buffer;

    Ali
    =?iso-8859-1?Q?Ali_=C7ehreli?=, Sep 20, 2005
    #5
  6. Patricia

    Patricia Guest

    Sorry, I just want to give a rough idea of the problem. As I said, I
    have to use the old library, and can't change it. The old library
    should look like below.

    class C : public A, public B {
    // no members
    };

    main() {
    // sizeof(A) = 16, sizeof(B) = 60, but sizeof(C) = 80
    C * pc = (C*) new char[sizeof(A) + sizeof(B)];
    new (pc) C();
    B * pb = (B*) pc;

    char * p1 = (char *) pc;
    char * p2 = (char *) pb;

    cout << "offset : " << (p2 - p1) << endl;

    delete [] (char *)pc;
    }

    It's strange that sizeof(C) > sizeof(A) + sizeof(B). The thing is when
    I declare my own A, B, C classes, it turns out sizeof(C) = sizeof(A) +
    sizeof(B).

    Is it because of a compiler option. By the way, I compile the program
    with CC on Solaris 8.
    Patricia, Sep 20, 2005
    #6
  7. "Patricia" <> wrote in message
    news:...

    > class C : public A, public B {
    > // no members
    > };
    >
    > main() {
    > // sizeof(A) = 16, sizeof(B) = 60, but sizeof(C) = 80
    > C * pc = (C*) new char[sizeof(A) + sizeof(B)];


    That is plain wrong. You can fit a C only on an area that has the size of at
    least sizeof(C). Please use placement new to construct a C on a
    pre-allocated memory. (I've already posted an example of this on this
    thread.)

    > It's strange that sizeof(C) > sizeof(A) + sizeof(B).


    The compiler is free use padding bytes between parts of objects.

    Ali
    =?iso-8859-1?Q?Ali_=C7ehreli?=, Sep 20, 2005
    #7
  8. Patricia

    Patricia Guest

    Sorry, the above code is simplified. The real situation is there is a
    class D which derives from B. Although C derives from A and B, they
    used a memory block to hold A and D as below:

    C * pc = (C*) new char[sizeof(A) + sizeof(D)];
    new (pc) C();
    B * pb = (B*) pc;

    char * p1 = (char *) pc;
    char * p2 = (char *) pb;

    cout << "offset : " << (p2 - p1) << endl;

    The code looks weird, but this is the real implemenation which was
    written between 1995 and 1999.

    Since A's size is 16, I don't know why the offset is 20.
    Patricia, Sep 20, 2005
    #8
  9. Patricia wrote:
    > First, I know the following code is bad, but it's from a library I have
    > to use, and I can't change it.
    >
    > class A {
    > // some primitive members
    > };
    >
    > class B {
    > // some primitive members
    > };
    >
    > class C : public A, B {
    > // no members
    > };
    >
    > main() {
    > // I know sizeof(A) = 16, sizeof(B) = 60
    > C * pc = (C*) new char[sizeof(A) + sizeof(B)];
    > B * pb = (B*) pc;
    >
    > char * p1 = (char *) pc;
    > char * p2 = (char *) pb;
    >
    > cout << "offset : " << (p2 - p1) << endl;
    > }
    >
    > What surprised me is that the offset is 20 instead of 16. Do you know
    > the possible reason?


    The compiler has adding padding between the A part of C and the B part of C.

    Object C is

    16 bytes of A
    4 bytes of padding
    60 bytes of B

    Compilers are allowed to do this, which is why you should not assume
    that sizeof(C) = sizeof(A) + sizeof(B)

    john
    John Harrison, Sep 20, 2005
    #9
  10. Patricia

    Old Wolf Guest

    Ali Çehreli wrote:
    > char * buffer = new char[sizeof(C)];
    >
    > C * pc = new (reinterpret_cast<void *>(buffer)) C();
    > B * pb = pc;
    > char * p1 = reinterpret_cast<char *>(pc);
    > char * p2 = reinterpret_cast<char *>(pb);
    >
    > // WARNING: Undefined behavior below! We cannot subtract two
    > // pointers unless they point to objects of the same
    > // array. Nevertheless, the results may be useful on a given system.
    > std::cout << "offset : " << (p2 - p1) << endl;


    p1 and p2 both point to within the object 'buffer' points to,
    so I think the behaviour is defined.
    Old Wolf, Sep 21, 2005
    #10
  11. Patricia

    red floyd Guest

    Old Wolf wrote:
    > Ali Çehreli wrote:
    >
    >> char * buffer = new char[sizeof(C)];
    >>
    >> C * pc = new (reinterpret_cast<void *>(buffer)) C();
    >> B * pb = pc;
    >> char * p1 = reinterpret_cast<char *>(pc);
    >> char * p2 = reinterpret_cast<char *>(pb);
    >>
    >> // WARNING: Undefined behavior below! We cannot subtract two
    >> // pointers unless they point to objects of the same
    >> // array. Nevertheless, the results may be useful on a given system.
    >> std::cout << "offset : " << (p2 - p1) << endl;

    >
    >
    > p1 and p2 both point to within the object 'buffer' points to,
    > so I think the behaviour is defined.
    >


    However, there's no need for reinterpret_cast. Assuming that p1 and p2
    point into the same object, use

    void *p1 = static_cast<void*>(pc);
    void *p2 = static_cast<void*>(pb);
    std::cout << (p2 - p1) << std::endl;
    red floyd, Sep 21, 2005
    #11
  12. "red floyd" <> wrote in message
    news:ke1Ye.5363$...
    > Old Wolf wrote:
    >> Ali Çehreli wrote:
    >>
    >>> char * buffer = new char[sizeof(C)];
    >>>
    >>> C * pc = new (reinterpret_cast<void *>(buffer)) C();
    >>> B * pb = pc;
    >>> char * p1 = reinterpret_cast<char *>(pc);
    >>> char * p2 = reinterpret_cast<char *>(pb);
    >>>
    >>> // WARNING: Undefined behavior below! We cannot subtract two
    >>> // pointers unless they point to objects of the same
    >>> // array. Nevertheless, the results may be useful on a given system.
    >>> std::cout << "offset : " << (p2 - p1) << endl;

    >>
    >>
    >> p1 and p2 both point to within the object 'buffer' points to,
    >> so I think the behaviour is defined.
    >>

    >
    > However, there's no need for reinterpret_cast. Assuming that p1 and p2
    > point into the same object, use
    >
    > void *p1 = static_cast<void*>(pc);
    > void *p2 = static_cast<void*>(pb);
    > std::cout << (p2 - p1) << std::endl;


    Unfortunately that won't work :( Cannot do pointer arithmetic on void*
    types.

    Ali
    =?iso-8859-1?Q?Ali_=C7ehreli?=, Sep 21, 2005
    #12
  13. Patricia

    Patricia Guest

    > The compiler has adding padding between the A part of C and the B part of C.
    >
    > Object C is
    >
    > 16 bytes of A
    > 4 bytes of padding
    > 60 bytes of B
    >
    > Compilers are allowed to do this, which is why you should not assume
    > that sizeof(C) = sizeof(A) + sizeof(B)


    It seems the compiler tries to make the size of a class dividable by 8.

    The thing is the library behaves as mentioned above, but the same
    compiler does not add padding for my own test code. I guess sizeof is
    calcuated at compile time. Is it common or possible for the same
    compiler behaves differently for different code ?
    Patricia, Sep 21, 2005
    #13
  14. Patricia

    Guest

    John Harrison wrote:
    > Patricia wrote:
    > > First, I know the following code is bad, but it's from a library I have
    > > to use, and I can't change it.
    > >
    > > class A {
    > > // some primitive members
    > > };
    > >
    > > class B {
    > > // some primitive members
    > > };
    > >
    > > class C : public A, B {
    > > // no members
    > > };
    > >
    > > main() {
    > > // I know sizeof(A) = 16, sizeof(B) = 60
    > > C * pc = (C*) new char[sizeof(A) + sizeof(B)];
    > > B * pb = (B*) pc;
    > >
    > > char * p1 = (char *) pc;
    > > char * p2 = (char *) pb;
    > >
    > > cout << "offset : " << (p2 - p1) << endl;
    > > }
    > >
    > > What surprised me is that the offset is 20 instead of 16. Do you know
    > > the possible reason?

    >
    > The compiler has adding padding between the A part of C and the B part of C.
    >
    > Object C is
    >
    > 16 bytes of A
    > 4 bytes of padding
    > 60 bytes of B
    >
    > Compilers are allowed to do this, which is why you should not assume
    > that sizeof(C) = sizeof(A) + sizeof(B)
    >
    > john


    This really sounds strange from a pragmatic point of view. I would
    rather expect that the C object is structured like:

    60 bytes of B
    4 bytes of padding
    16 bytes of A

    I am unsure if the compiler is allowed to store the objects in that
    way, but if not the more likely situation is that C was declared as
    public B, public A.

    /Peter
    , Sep 21, 2005
    #14
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John Grandy
    Replies:
    0
    Views:
    609
    John Grandy
    Sep 13, 2005
  2. Dave
    Replies:
    3
    Views:
    390
    Kevin Goodsell
    Apr 19, 2004
  3. WU FUHENG
    Replies:
    1
    Views:
    443
    Diez B. Roggisch
    Feb 5, 2004
  4. Replies:
    38
    Views:
    1,274
    Dennis Lee Bieber
    Feb 15, 2005
  5. ian douglas
    Replies:
    2
    Views:
    981
    Randy Howard
    Jul 30, 2004
Loading...

Share This Page