g++ 实现如何处理这种情况?

6
这是关于此问题的后续提问:这个问题
考虑以下例子:
#include <iostream>

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;
    std::cout << "pC == " << std::hex << pC << "\n";
    std::cout << "pA == " << std::hex << pA << "\n";
    return 0;
}

使用Visual Studio 2010,输出结果是(在我的电脑上):

pC == 002DEF90
pA == 002DEF94

(这可以通过问题的被接受的答案来解释)。

使用g++,输出结果如下

pC == 0x96c8008
pA == 0x96c8008

因此,问题是,g++的实现如何处理这种情况? 什么使得当C应该有一个虚函数表时,地址相同?(我知道这是一个实现细节,不要说 :) 我出于好奇对这个实现细节感兴趣)。


@Naszta:那会违反“不为你不使用的东西付费”的规则。我认为虚函数表存储在其他地方(也许是在末尾)。如Rob所说,-S将提供答案。 - Skizz
1
将任何字段添加到基类“A”会改变这种行为。似乎,g++只有在它们为空时才可以自由地将基类放置在vtable指针之前。可能与空基类优化有关。 - Evgeny Kluev
如果一个类没有虚函数,那么就不需要有虚函数表。问题中的A类没有虚函数。你不应该期望A类的实例有虚函数表。 - Skizz
1
有人知道为什么vptr被放在开头而不是在A子对象之后吗?一个实现是否可以通过将vptr放在不同的位置来节省一些指针调整/转发开销呢? - wolfgang
@wolfgang,vtable指针放在开头是为了避免指针调整。典型情况不是从一种类型转换为子类型(或反之亦然)。典型情况是在一个类型上调用虚函数。如果将vtable指针放在开头,调用虚函数时就可以跳过偏移对象指针的成本。即“objPtr->vptr”映射到“objPtr”,而不是“(objPtr + N)”。 - Derek Park
显示剩余13条评论
1个回答

5
经过一番努力,我终于想起了一些东西。
空基类优化(Empty Base Optimization)。
只要 A 有成员,结果就会改变。然而只要它没有成员,编译器就不需要为 A 生成真正的布局,重要的是保证每个 A “对象”都有一个与任何其他 A 对象不同的地址。
因此,编译器只需使用 B 子对象(从 A 继承)的地址作为合适的地址。结果发现 B 和 C 具有相同的地址(第一个基类 + 都有虚方法)。
另一方面,如果 A 有成员或者 B 的第一个成员是 A(还有其他条件),那么 EBO 就不能再应用了,你会注意到地址上的跳跃。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接