7

在下面的代码中,pC == pA:

class A
{
};

class B : public A
{
public:
    int i;
};

class C : public B
{
public:
    char c;
};

int main()
{
    C* pC = new C;
    A* pA = (A*)pC;

    return 0;
}

但是当我在 B 中添加一个纯虚函数并在 C 中实现它时,pA != pC:

class A
{
};

class B : public A
{
public:
    int i;
    virtual void Func() = 0;
};

class C : public B
{
public:
    char c;
    void Func() {}
};

int main()
{
    C* pC = new C;
    A* pA = (A*)pC;

    return 0;
}

为什么在这种情况下 pA 不等于 pC?他们不是都指向内存中的同一个“C”对象吗?

4

3 回答 3

6

您会看到指针的不同值,因为新的虚函数导致将 vtable 指针注入到您的对象中。VC++ 将 vtable 指针放在对象的开头(这是典型的,但纯粹是内部细节)。

让我们在 A 中添加一个新字段,以便于解释。

class A {
public:
    int a;
};
// other classes unchanged

现在,在内存中,您的pAA看起来像这样:

pA --> | a      |          0x0000004

将 B 和 C 添加到混合物中后,您将得到以下结果:

pC --> | vtable |          0x0000000
pA --> | a      |          0x0000004
       | i      |          0x0000008
       | c      |          0x000000C

如您所见,pA它指向的是 vtable之后的数据,因为它对 vtable 或如何使用它一无所知,甚至不知道它在那里。 pC确实知道vtable,所以它直接指向表,这简化了它的使用。

于 2012-04-03T22:37:03.867 回答
3

指向对象的指针可以转换为指向基对象的指针,反之亦然,但转换不一定是微不足道的。基指针与派生指针具有不同的是完全可能的,而且通常是必要的。这就是为什么你有一个强大的类型系统和转换。如果所有指针都相同,那么您也不需要。

于 2012-04-03T22:11:32.700 回答
0

这是我的假设,基于这个问题。

1)您有一个案例,您从 C 转换为 A 并且您得到了预期的行为。
2)您添加了一个虚拟函数,并且该强制转换不再有效(因为您不能再在强制转换到 A 之后直接从 A 中提取数据,您获得的数据对您来说毫无意义)。

如果这些假设是真的,那么您遇到的困难是在 B 中插入虚拟表。这意味着类中的数据不再与基类中的数据完美对齐(因为在类中添加了字节,对您隐藏的虚拟表)。一个有趣的测试是检查 sizeof 以观察未知字节的增长。

要解决此问题,您不应直接从 A 转换为 C 来收集数据。您应该在 A 中添加一个由 B 和 C 继承的 getter 函数。

鉴于您在评论中的更新,我认为您应该阅读这篇文章,它解释了虚拟表和内存布局,以及它如何依赖于编译器。该链接更详细地解释了我上面解释的内容,但给出了指针为不同值的示例。真的,我知道你为什么问错了这个问题,但似乎信息仍然是你想要的。从 C 到 A 的转换考虑了此时的虚拟表(注意 C-8 是 4,在 32 位系统上,我相信这将是虚拟表所需的地址大小)。

于 2012-04-03T22:13:55.867 回答