4

我正在阅读《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 上都测试过,结果是一样的。所以我认为微软选择这样的设计肯定是有原因的。

而且,如果你这样做:

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

显然&Derived::v2不是0。为什么编译器要区分这两种情况?

谁能告诉背后发生的事情?谢谢!

- 编辑 -

对于那些认为 &Derived::v1 没有得到有效地址的人。你从来没有这样做过吗?

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

4 回答 4

5

发帖人问我这个,起初我也怀疑类似的错误原因。这不是 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 <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

}
于 2013-04-01T23:38:15.640 回答
2

当你这样做时&Derived::v2,你没有得到一个有效的地址,因为你没有一个有效的对象。但是,在第二种情况下,您将获得类中成员的偏移量Derived,这意味着如果您创建了一个类型的对象,v2它将在内存中存储四个字节。v1Derived

于 2013-03-20T09:42:01.117 回答
0

&Derived::v1并且&Derived::v2ints,因此它们的长度为 4 个字节。当您将这些表达式之一分配给时,您正在打印的p是它们从指针到Derived类实例的偏移量。

于 2013-03-20T09:51:25.007 回答
0

我知道的大多数信息都特别提到了指向成员函数的指针,尽管我不知道指向成员数据的指针的实现方式会有什么不同。

指向涉及多重继承的成员函数的指针通常实现为包含函数指针(始终指向派生类位置)和偏移量的结构,以管理派生类的 this 指针与this 指针。偏移量被添加到隐藏的 this 参数以说明派生类。您所看到的是偏移量的变化取决于指向成员的指针的类型,并在 Herb Sutter 的回答中雄辩地描述:当类型为 Base2::* 时,偏移量为 0,但当类型为派生时,偏移量为: :* 是 4。

有关实现细节的更多信息,我建议阅读 Raymond Chen 的一些博客文章(从 2004 年开始,细节可能从那时起发生了变化),其中提出问题并在此处回答。这些帖子还将解释为什么 sizeof() 可以为指向成员的指针返回有趣的结果。

于 2013-04-02T00:10:44.510 回答