Reputation: 4219
I was asked this interview question and I got it wrong. "What is the output": My answer was 135, the actual output is 136. This implies that the pointer's to the two parent classes are not equal even though they pass the prior test of being equal to the child class. I thought I understood c++ pointers, but this has me stumped for an explanation. Although I think I see what's going on, I'm not sure why. Any c++ experts out there that can offer a technical explanation? It appears the first two comparisons are more logical in nature, while the last comparison is more literal...
#include <iostream>
class A
{
public:
A() : m_i(0) { }
protected:
int m_i;
};
class B
{
public:
B() : m_d(0.0) { }
protected:
double m_d;
};
class C
: public A, public B
{
public:
C() : m_c('a') { }
private:
char m_c;
};
int main()
{
C c;
A *pa = &c;
B *pb = &c;
const int x = (pa == &c) ? 1 : 2;
const int y = (pb == &c) ? 3 : 4;
const int z = (reinterpret_cast<char*>(pa) == reinterpret_cast<char*>(pb)) ? 5 : 6;
std::cout << x << y << z << std::endl;
return 0;
}
Upvotes: 14
Views: 874
Reputation: 5223
C++ typically lays out class structures by one after another. Lets check following code sample:
#include <iostream>
#include <stdio.h>
//#define DO_PACKSTRUCTURES
//#define DEFINE_VFUNC
#ifdef DO_PACKSTRUCTURES
#pragma pack(push, 1)
#endif
class A
{
public:
A() : m_i(0) { }
#ifdef DEFINE_VFUNC
virtual
#endif
void myfunc()
{
}
protected:
int m_i;
};
class B
{
public:
B() : m_d(0.0) { }
#ifdef DEFINE_VFUNC
virtual
#endif
void myfunc2()
{
}
protected:
double m_d;
};
class C
: public A, public B
{
public:
C() : m_c('a') { }
#ifdef DEFINE_VFUNC
virtual
#endif
void myfunc3()
{
}
char m_c;
};
#ifdef DO_PACKSTRUCTURES
#pragma pack(pop)
#endif
void pprint(char* prefix, void* p)
{
printf("%s = %p\r\n", prefix, p);
}
int main()
{
C c;
A *pa = &c;
B *pb = &c;
pprint("&c", &c);
pprint("pa", pa);
printf("\r\n");
pprint("pb", pb);
pprint("pa + sizeof(A)", ((char*)pa) + sizeof(A));
printf("\r\n");
pprint("&c.m_c", &c.m_c);
pprint("pb + sizeof(B)", ((char*)pb) + sizeof(B));
printf("\r\n");
printf("sizeof(A)=%d\r\n", sizeof(A));
printf("sizeof(B)=%d\r\n", sizeof(B));
printf("sizeof(C)=%d\r\n", sizeof(C));
printf("sizeof(int)=%d\r\n", sizeof(int));
printf("sizeof(double)=%d\r\n", sizeof(double));
printf("sizeof(char)=%d\r\n", sizeof(char));
printf("sizeof(void*)=%d\r\n", sizeof(void*));
pa->myfunc();
c.myfunc2();
c.myfunc3();
return 0;
}
Win32 / Debug or Release / DO_PACKSTRUCTURES & DEFINE_VFUNC are undefined:
&c = 00BBF7A4
pa = 00BBF7A4
pb = 00BBF7AC
pa + sizeof(A) = 00BBF7A8
& c.m_c = 00BBF7B4
pb + sizeof(B) = 00BBF7B4
sizeof(A) = 4
sizeof(B) = 8
sizeof(C) = 24
sizeof(int) = 4
sizeof(double) = 8
sizeof(char) = 1
sizeof(void*) = 4
So pointer to C* is the same pointer as A* - because A is first base class from which memory layout starts. So memory layout looks somehow like this:
C*:
A
B
C members (m_c)
pb is not equal to ( pa + sizeof(A) ) - because compiler add some alignment bytes between A and B to speed up access to B. Not sure how important those optimizations are - making millions of instances of same class may be will have performance impact.
Win32 / Debug or Release / DO_PACKSTRUCTURES is defined & DEFINE_VFUNC is undefined:
&c = 00B9F770
pa = 00B9F770
pb = 00B9F774
pa + sizeof(A) = 00B9F774
&c.m_c = 00B9F77C
pb + sizeof(B) = 00B9F77C
sizeof(A)=4
sizeof(B)=8
sizeof(C)=13
sizeof(int)=4
sizeof(double)=8
sizeof(char)=1
sizeof(void*)=4
Now we are not adding any alignment or padding bytes (Because of #pragma pack(push, 1) ) - we got C class smaller, and also now pb == (pa + sizeof(A)). Now we can see also what is "allocating" C - class that is sizeof(int) / sizeof(double) + sizeof(char) = 4 + 8 + 1 = 13.
Win32 / Debug or Release / DO_PACKSTRUCTURES & DEFINE_VFUNC defined:
&c = 007EFCF4
pa = 007EFCF4
pb = 007EFCFC
pa + sizeof(A) = 007EFCFC
&c.m_c = 007EFD08
pb + sizeof(B) = 007EFD08
sizeof(A)=8
sizeof(B)=12
sizeof(C)=21
sizeof(int)=4
sizeof(double)=8
sizeof(char)=1
sizeof(void*)=4
We still have pointer calculation match as in previous case, but if we calculate size using sizeof(class) - we will get correct size, but not sizeof(member type) - because of 'virtual' keyword - virtual keyword itself is allocating extra virtual table pointer size.
It reflects 21 - 8 - 4 - 1 = 8
8 / sizeof(void*) = 2 - that's two vtables - one from A and another from B class instances.
I'm not sure why C class itself does not have it's own vtable - to my best understanding it should. This is something to figure out later on. Microsoft Visual C++ compiler has also special kind of keyword __declspec (novtable) which has also some reflection on how vtable's are generated. But is something normal developers don't need, unless you're dealing with advanced COM programming.
Upvotes: 2
Reputation: 7324
Perhaps if you print out pa
and pb
it will be more clear what is going on.
std::cout << "A: " << pa << std::endl;
std::cout << "B: " << pb << std::endl;
std::cout << "C: " << &c << std::endl;
Running this at the end of main
gives me
A: 0xbfef54e0
B: 0xbfef54e4
C: 0xbfef54e0
(your output may be different, the important thing is that they aren't all equal)
This is because of how a C
object is represented in memory. Since a C
is both an A
and a B
, it needs to have the data members from each part. The real layout of C
is something like this (ignoring padding):
int A::m_i;
double B::m_d;
char C::m_c;
When you convert a C*
to an A*
, the compiler knows that the A
part starts at offset 0, so the pointer value doesn't change. For C*
to B*
it needs to offset by sizeof(int)
(plus padding). This offset handling is done automatically for you for calculating x
and y
. For z
it is bypassed since you use reinterpret_cast
Upvotes: 8