简体   繁体   中英

Address of C++ pointer to class data member in Visual Studio

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;

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 not int Derived::* , but int Base2::* , which naturally does have an offset of zero because it's the offset with respect to Base2. When you explicitly convert it to an int 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.

struct Base1 { int a; };
struct Base2 { int b; };
struct Derived : Base1, Base2 { };

#include <cassert>
#include <cstdio>
#include <typeinfo>
using namespace std;

int main () {

   printf( "%s\n", typeid(&Derived::a).name() );  // mentions Base1
   printf( "%s\n", typeid(&Derived::b).name() );  // mentions Base2

   int Derived::* pdi = &Derived::b;  // OK
   int Base2::*   p2i = &Derived::b;  // OK
   //int Base1::* p1i = &Derived::b;  // ERROR

   assert( sizeof(int*) == sizeof(pdi) );
   printf( "%p %p", p2i, pdi );  // prints "(nil) 0x4" using GCC 4.8 at liveworkspace.org

}

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 the Derived class, meaning that v2 would be stored four bytes after v1 in memory if you created an object of type Derived .

&Derived::v1 and &Derived::v2 are int s and so they are 4 bytes long. What you are printing when you assign one of these expressions to p is their offset from the pointer to an instance of the Derived class.

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 technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM