I'm reading the book Inside the C++ Object Model. In the book there's an example like:
struct Base1
{
int v1;
};
struct Base2
{
int v2;
};
class Derived : public Base1, public Base2 {};
printf("&Derived::v1 = %p\n", &Derived::v1); // Print 0 in VS2008/VS2012
printf("&Derived::v2 = %p\n", &Derived::v2); // Print 0 in VS2008/VS2012
In the previous code, the print of address Derived::v1 & Derived::v2 will both be 0. However, if print the same address via a variable:
int Derived::*p;
p = &Derived::v1;
printf("p = %p (&Derived::v1)\n", p); // Print 0 in VS2008/VS2012 as before
p = &Derived::v2;
printf("p = %p (&Derived::v2)\n", p); // Print 4 in VS2008/VS2012
By examining the size of &Derived::v1 and p, I get 4 in both.
// Both are 4
printf("Size of (&Derived::v1) is %d\n", sizeof(&Derived::v1));
printf("Size of p is %d\n", sizeof(p));
The address of Derived::v1 will be 0, but the address of Derived::v2 will be 4. I don't understand why &Derived::v2 became 4 when assign it to a variable.
Examine the assembly code, when directly query the address of Derived::v2, it is translated to a 0; but when assign it to a variable, it gets translated to a 4.
I tested it on both VS2008 & VS2012, the result is the same. So I think there's must be some reason to make Microsoft choose such design.
And, if you do like this:
d1.*(&Derived::v2) = 1;
Apparently &Derived::v2 is not 0. Why does the compiler distinguish this two cases?
Can anyone please tell the thing happens behind? Thank you!
--Edit--
For those think the &Derived::v1 doesn't get a valid address. Haven't you ever did this?
Derived d1, d2;
d1.*p = 1;
d2.*p = 1;
&Derived::v1
and&Derived::v2
areint
s and so they are 4 bytes long. What you are printing when you assign one of these expressions top
is their offset from the pointer to an instance of theDerived
class.When you're doing
&Derived::v2
you're not getting a valid address as you don't have a valid object. In the second case though, you get the offset of the members in theDerived
class, meaning thatv2
would be stored four bytes afterv1
in memory if you created an object of typeDerived
.Most of the information I'm aware of specifically mentions pointer to member functions, though I'm not aware of any reason pointers to member data would be implemented any differently.
Pointer to member functions where multiple inheritance is involved are often implemented as a struct that contains the function pointer (which always points to the derived class location) and an offset to manage the case when the this pointer for the derived class is not the same as the this pointer. The offset is added to the hidden this parameter to account for the derived class. What you are seeing is the offset changing depending on the type of pointer to member and eloquently described in Herb Sutter's answer: the offset from this when the type is Base2::* is 0, but the offset from this when the type is Derived::* is 4.
For more information about implementation details, I recommend reading through some of Raymond Chen's blog posts (from 2004, details may have changed since then), in which the question is posed here and answered here. These posts will also explain why sizeof() can return interesting results for pointers to members.
The poster asked me about this, and at first I also suspected similar wrong causes. This is not specific to VC++.
It turns out that what's happening is that the type of
&Derived::v2
is notint Derived::*
, butint Base2::*
, which naturally does have an offset of zero because it's the offset with respect to Base2. When you explicitly convert it to anint Derived::*
, the offset is corrected.Try this code on VC++ or GCC or Clang... I'm sticking with stdio/printf as the poster was using.