C++虚拟继承内存布局

9

虚继承的内存布局

我试图全面了解虚继承和vTables/vPtrs等底层内存机制。

我编写了两个代码示例,理解它们工作的原因。但是,我希望确保自己对对象内存布局的想法是正确的。

这里 有两个示例的图片,我只想知道我对涉及的内存布局的想法是否正确。

示例1:

class Top { public: int a;  };
class Left : public virtual Top {  public: int b; };
class Right : public virtual Top { public: int c; };
class Bottom : public Left, public Right { public:  int d; };

输入图像描述

示例2:

与上述示例相同,但是包含以下内容:

class Right : public virtual Top {
public:
    int c;
    int a;  // <======= added this
};

enter image description here


1
一个好的起点是Itanium ABI,因为这是所有实现特定的。 - Mgetz
@Mgetz,我有点不确定为什么你会建议从Itanium ABI开始,因为x86或amd64 ABI在统计上更有可能与原始问题相关 - 或者也许我在问题中漏掉了一些标识Itanium平台的东西...? - twalberg
3
由于Itanium ABI即使在X86-64上被用作C++的标准ABI,因此需要进行翻译。 - Mgetz
可能是虚拟继承中的虚拟表和内存布局的重复问题。 - Suma
你的问题似乎有矛盾之处:Top 应该是多态的吗? - curiousguy
@Dominic,在你的Example 2中,底部类布局中Right::a不应该出现在Right::c之后吗? - infinite loop
1个回答

1

关于对象布局,C++标准并没有提出很多规定。虚函数表(vtable)和虚基类指针甚至不是标准的一部分。因此,问题和答案只能说明可能的实现方法。

从您的图纸中快速浏览看来,您的布局是正确的。

您可能会对以下进一步参考资料感兴趣:

编辑:Bottom继承Right::a还是Left::a

在你的测试2中, Right 和 Left 共享同一个公共父类 Top 。因此,在 Bottom 中只有一个子对象 Top ,因此只有一个相同的 Top :: a 。
有趣的是,在您的测试2中引入了一个 Right 中的成员 a 。这是一个与从 Top 继承的 a 不同的 a 。它是一个独立的成员,并且仅具有另一个成员“巧合地”具有相同的名称。因此,如果通过 Right 访问 a ,则 Right :: a 隐藏 Top :: a (顺便说一下,这也是 Bottom :: Top :: a , Right :: Top :: a , Left :: Top :: a )。 在这种情况下,bottom.a表示Right :: a,不是因为对象布局,而是因为名称查找(和隐藏)规则。而且这不是实现相关的:它是标准且可移植的。

这里是一个变体,用于演示测试2的情况:

int main() {
    Bottom bottom; 
    bottom.a = 7; 
    cout << bottom.Top::a << endl << bottom.Left::Top::a << endl;
    cout << bottom.Right::Top::a << endl << bottom.Left::a << endl;
    cout << bottom.Right::a << endl <<endl;
    bottom.Right::a = 4; 
    bottom.Left::a = 3; 
    cout << bottom.Top::a << endl << bottom.Left::Top::a << endl;
    cout << bottom.Right::Top::a << endl << bottom.Left::a << endl;
    cout << bottom.Right::a << endl <<endl;

    cout << "And the addresses are: " << endl; 
    cout << " Bottom:       " << (void*)&bottom << endl; 
    cout << " Top:          " << (void*)static_cast<Top*>(&bottom) << endl;
    cout << " Left:         " << (void*)static_cast<Left*>(&bottom) << endl;
    cout << " Right:        " << (void*)static_cast<Right*>(&bottom) << endl;
    cout << " Top::a:       " << (void*)&bottom.Top::a << endl;
    cout << " Left::Top::a: " << (void*)&bottom.Left::Top::a << endl;
    cout << " Right::Top::a:" << (void*)&bottom.Right::Top::a << endl;
    cout << " Left::a:      " << (void*)&bottom.Left::a << endl;
    cout << " Rigth::a:     " << (void*)&bottom.Right::a << endl;
}; 

非常感谢。不过,关于测试2中底部布局的问题 - Bottom 只继承了 Right::a 吗?因为它是最近被覆盖的,还是同时继承了两者,并使用一些逻辑来确定哪一个应该作为 bottom.a 使用?非常感谢。 - Dominic Farolino
@DomFarolino 我已经编辑了我的答案,以澄清这一方面。 - Christophe
啊,好的,我知道声明 Right::a 会使 Bottom.a = Right::a 是因为它是最近覆盖的属性。因此,Bottom.Top::aBottom.Right::Top::aBottom.Left::Top::aBottom.aBottom::Right::a 不同,但我不知道这是否仅仅是名称查找的原因,还是与内存布局有关。我会在有机会时查看你的示例。 - Dominic Farolino
1
只有虚函数可以被覆盖。 - curiousguy
1
@DataDancer DomFarolino 隐藏数据成员? - curiousguy
显示剩余3条评论

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