【问题标题】:Address of C++ pointer to class data member in Visual StudioVisual Studio 中指向类数据成员的 C++ 指针的地址
【发布时间】:2013-03-20 09:38:21
【问题描述】:

我正在看书Inside the C++ Object Model。书中有一个例子:

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;

【问题讨论】:

  • 你不是打印出指向变量的指针的地址吗?我不认为你创建 Base1/Base 2 的对象。
  • 你得到未定义的行为指向成员的指针 不是指针,您不能使用printf%p 格式说明符来显示它。

标签: c++ visual-studio pointers


【解决方案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 <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

}

【讨论】:

  • 但这是预期的行为。?为什么&amp;Derived::b 的类型为int Base::*.?对此有何标准?
【解决方案2】:

当您执行&amp;Derived::v2 时,您没有获得有效的地址,因为您没有有效的对象。不过,在第二种情况下,您将获得 Derived 类中成员的 offset,这意味着如果您创建了一个类型为Derived.

【讨论】:

  • 但是v2应该跟随v1所以偏移量不应该是0,对吧?
【解决方案3】:

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

【讨论】:

  • 是的。但我想知道为什么直接打印和分配给变量时偏移量不同。
【解决方案4】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-24
    • 1970-01-01
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多