Адрес указателя C ++ для члена данных classа в Visual Studio

Я читаю книгу « Внутри объектной модели C ++» . В книге есть пример:

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 

В предыдущем коде печать адреса Derived :: v1 & Derived :: v2 будет равна 0 . Однако, если вы печатаете один и тот же адрес через переменную:

 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 

Изучая размер & Derived :: v1 и p, я получаю 4 в обоих.

 // Both are 4 printf("Size of (&Derived::v1) is %d\n", sizeof(&Derived::v1)); printf("Size of p is %d\n", sizeof(p)); 

Адрес Derived :: v1 будет равен 0 , но адрес Derived :: v2 будет равен 4 . Я не понимаю, почему & Derived :: v2 стал 4 при назначении переменной.

Изучите код сборки, когда напрямую запрашиваете адрес Derived :: v2, он переводится в 0 ; но при назначении переменной переменной она переводится в 4 .

Я тестировал его как на VS2008, так и на VS2012, результат тот же. Поэтому я считаю, что для того, чтобы Microsoft выбрала такой дизайн, должна быть какая-то причина.

И если вы сделаете так:

 d1.*(&Derived::v2) = 1; 

Очевидно, что & Derived :: v2 не равно 0 . Почему компилятор отличает эти два случая?

Может ли кто-нибудь сказать, что происходит? Спасибо!

–Редактировать–

Для тех, кто думает, что & Derived :: v1 не получает действительный адрес. Разве вы этого не делали?

 Derived d1, d2; d1.*p = 1; d2.*p = 1; 

Плакат спросил меня об этом, и сначала я также подозревал подобные неправильные причины. Это не относится к VC ++.

Оказывается, что происходит то, что тип &Derived::v2 не является int Derived::* , но int Base2::* , который, естественно, имеет смещение нуля, потому что это смещение относительно Base2. Когда вы явно конвертируете его в int Derived::* , смещение корректируется.

Попробуйте этот код на VC ++ или GCC или Clang … Я придерживаюсь stdio / printf, который использовал плакат.

 struct Base1 { int a; }; struct Base2 { int b; }; struct Derived : Base1, Base2 { }; #include  #include  #include  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 } 

Когда вы делаете &Derived::v2 вы не получаете действительный адрес, так как у вас нет действительного объекта. Во втором случае, однако, вы получаете смещение членов в classе Derived , а это означает, что v2 будет храниться в четырех байтах после v1 в памяти, если вы создали объект типа Derived .

&Derived::v1 и &Derived::v2 являются int s и поэтому они имеют длину 4 байта. То, что вы печатаете, когда вы назначаете одно из этих выражений на p является их смещением от указателя к экземпляру classа Derived .

Большая часть информации, которую я знаю, специально упоминает указатель на функции-члены, хотя я не знаю ни одной причины, указывающей на данные члена, будет реализован по-разному.

Указатель на функции-члены, в которых участвует множественное наследование, часто реализуется как структура, содержащая указатель на функцию (который всегда указывает на местоположение производного classа) и смещение для управления случаем, когда этот указатель для производного classа не является таким же, как этот указатель. Смещение добавляется к скрытому этому параметру для учета производного classа. То, что вы видите, это смещение, изменяющееся в зависимости от типа указателя на член и красноречиво описанного в ответе Герба Саттера: смещение от этого, когда тип Base2 :: * равен 0, но смещение от этого, когда тип имеет значение Derived: : * 4.

Для получения дополнительной информации о деталях реализации я рекомендую прочитать некоторые из сообщений блога Раймонда Чена (с 2004 года, возможно, с тех пор, возможно, изменились данные), в которых здесь задан вопрос и ответили здесь . Эти сообщения также объясняют, почему sizeof () может возвращать интересные результаты для указателей на членов.