strange multi-inheritance problem

P

Patricia

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.
 
?

=?iso-8859-1?Q?Ali_=C7ehreli?=

Patricia said:
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?=

Patricia said:
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?=

// 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
 
P

Patricia

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.
 
?

=?iso-8859-1?Q?Ali_=C7ehreli?=

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
 
P

Patricia

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.
 
J

John Harrison

Patricia said:
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
 
O

Old Wolf

Ali said:
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.
 
R

red floyd

Old said:
Ali said:
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;
 
?

=?iso-8859-1?Q?Ali_=C7ehreli?=

red floyd said:
Old said:
Ali said:
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
 
P

Patricia

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 ?
 
P

peter.koch.larsen

John said:
Patricia said:
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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,050
Latest member
AngelS122

Latest Threads

Top