2013-03-20 71 views
4

我在看书Inside the C++ Object Model。在这本书中有类似的例子:C++指针指向Visual Studio中的类数据成员的地址

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 

在前面的代码,地址的派生打印:: V1 &衍生:: V2都将是。但是,如果通过可变打印相同的地址:

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 

通过检查&衍生大小:: 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)); 

衍生:: V1的地址将是,但衍生:: V2的地址将是。我不明白为什么& Derived :: v2变成时,将它分配给一个变量。

检查汇编代码,当直接查询Derived :: v2的地址时,它被转换为;但是当将其分配给变量时,它将被转换为。

我在VS2008 & VS2012上测试过它,结果是一样的。所以我认为必须有一些理由让微软选择这样的设计。

而且,如果你这样做:

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

显然&衍生:: V2不。为什么编译器会区分这两种情况?

任何人都可以请告诉事情发生在后面?谢谢!

- 编辑 -

对于那些认为&衍生:: V1没有得到一个有效的地址。你没有这样做过吗?

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

您不打印出指向变量的指针地址吗?我不认为你创建Base1/Base 2的对象。 – ATaylor 2013-03-20 09:40:31

+1

你正在获取_undefined behavior_。指向member_的指针不是一个指针,你不能用'printf'的'%p'格式说明符来显示它。 – 2013-04-01 09:27:19

回答

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 

} 
+0

但它是一种预期的行为。?为什么'&Derived :: b'的类型是'int Base :: *'。什么标准对此说。 – 2017-01-08 14:55:23

2

当你在做&Derived::v2你没有得到一个有效的地址,你没有一个有效的对象。在第二种情况下,如果您创建了类型为Derived的对象,则类别中的类别的Derived类中的成员将获得,这意味着v2将在内存中的v1后四个字节中存储。

+0

但是v2应该跟随v1,所以偏移量不应该是0,对吗? – 2013-03-20 10:01:47

0

&Derived::v1&Derived::v2int s,所以它们是4字节长。当您将这些表达式中的一个指定给p时,您正在打印的是它们从指针到Derived类的实例的偏移量。

+0

是的。但我想知道为什么直接打印和分配给变量时偏移量是不同的。 – 2013-03-20 10:00:51

0

大部分我所知道的信息,特别提到指针成员函数,虽然我不知道有任何理由指向成员的数据会被不同的方式实现的任何。

指向涉及多重继承的成员函数的指针通常作为包含函数指针(总是指向派生类位置)的结构实现,并且该偏移量用于管理派生类的此指针为不同于这个指针。将偏移量添加到隐藏的此参数中以考虑派生类。你所看到的偏移量会根据指向成员的指针类型而改变,并且在Herb Sutter的回答中雄辩地描述:当类型为Base2 :: *时,偏移量为0,但是当类型为派生时与此偏移量为: :*是4.

有关实施细节的更多信息,我建议您阅读Raymond Chen的部分博文(从2004年开始,详情可能已更改),其中问题提交的地址为hereanswered here。这些帖子也将解释为什么sizeof()可以为成员指针返回有趣的结果。