【问题标题】:Itanium C++ ABI primary virtual basesItanium C++ ABI 主要虚拟基础
【发布时间】:2017-11-10 00:44:36
【问题描述】:

我正在阅读 here 关于如何选择主要碱基:

"...2.如果C是动态类类型:

一个。识别所有直接或间接的虚拟基类,它们是某些其他直接或间接基类的主要基类。调用这些间接的主要基类。

b.如果 C 具有动态基类,则尝试选择主要基类 B。如果存在,它是第一个(按直接基类顺序)非虚拟动态基类。否则,它是一个几乎为空的虚拟基类,(预序)继承图顺序中的第一个,如果存在则不是间接主基类,或者如果它们都是间接主基类,则只是第一个......"

在进行了这个更正之后:

“上面的情况(2b)现在认为是设计上的错误。使用第一个间接主基类作为派生类的主基并没有节省对象的任何空间,而且会造成一些重复基类虚拟表的附加副本中的虚拟函数指针。

好处是使用派生类的虚指针作为基类的虚指针,往往会节省负载,调用它的虚函数也不需要调整this指针。

人们认为 2b 会允许编译器在某些情况下避免调整它,但这是不正确的,因为虚函数调用算法要求通过指向定义函数的类的指针来查找函数,而不是一个只是继承它。 删除该要求不是一个好主意,因为这样就不再有办法使用它们跳转到的函数发出所有 thunk。 例如,考虑这个例子:

struct A { virtual void f(); };

结构 B : 虚拟公共 A { int i; };

结构 C : 虚拟公共 A { int j; };

结构 D : 公共 B, 公共 C {};

当声明 B 和 C 时,A 在每种情况下都是主基,因此尽管在 A-in-B 和 A-in-C vtables 中分配了 vcall 偏移量,但不需要进行此调整,也不会生成 thunk .但是,在 D 对象内部,A 不再是 C 的主要基础,因此如果我们允许对 C::f() 的调用使用 C 子对象中 A 的 vtable 副本,我们需要将其从 C* 调整为B::A*,这需要第三方 thunk。因为我们要求对 C::f() 的调用首先转换为 A*,所以 C-in-D 的 A 的 vtable 副本永远不会被引用,所以这不是必需的。”

您能否举个例子解释一下这指的是什么:“删除该要求不是一个好主意,因为这样就没有办法用它们跳转到的函数发出所有thunk了强>”?

另外,什么是第三方thunk

我也不明白引用的示例试图显示什么。

【问题讨论】:

  • 我认为“主要基础”的想法太多:一个静态,这意味着对象和 vtable 的给定布局;一个动态的,它描述了一个动态类型的对象。这已经让我很困惑了!

标签: c++ vtable abi virtual-inheritance thunk


【解决方案1】:

A 是一个几乎是空的类,它只包含一个 vptr 而没有可见的数据成员:

struct A { virtual void f(); };

A的布局是:

A_vtable *vptr

B 有一个几乎为空的基类用作“主要”:

struct B : virtual public A { int i; };

这意味着B 的布局以A 的布局开始,因此指向B 的指针就是指向A 的指针(在汇编语言中)。 B子对象的布局:

B_vtable *A_vptr
int i

A_vptr 显然会指向一个B vtable,它与A vtable 二进制兼容。

B_vtable 扩展了A_vtable,添加了导航到虚拟基类A 所需的所有信息。

B完整对象的布局:

A base_subobject
int i

C 也一样:

C_vtable *A_vptr
int j

C完整对象的布局:

A base_subobject
int j

D中显然只有一个A子对象,所以一个完整对象的布局是:

A base_subobject
int i
not(A) not(base_subobject) aka (C::A)_vptr
int j

not(A)A 几乎为空的基类的表示,即A 的vptr,但不是真正的A 子对象:它看起来像A,但可见@987654354 @是上面的两个字。是鬼A

(C::A)_vptr 是 vptr 到 vtable 的 vtable,其布局 vtable 用于 C(因此也有一个布局 vtable 用于 A),但对于 C 子对象,其中 A 最终不是主要基础: C 子对象已失去承载 A 基类的特权。所以很明显,通过(C::A)_vptr 对定义A 的虚拟函数(只有一个:A::f())的虚拟调用需要this 调整,带有一个接收指向not(base_subobject) 的指针的thunk“C::A::f()”和将其调整为A 类型的真实base_subobject,即(示例中上面的两个词)。 (或者如果D 中有一个覆盖器,那么D 对象在完全相同的地址,在示例中是上面的两个字。)

所以给出这些定义:

struct A { virtual void f(); };
struct B : virtual public A { int i; };
struct C : virtual public A { int j; };
struct D : public B, public C {};

应该使用不存在的A 主要基础的幽灵左值吗?

D d;
C *volatile cp = &d;
A *volatile ghost_ap = reinterpret_cast<A*> (cp);
ghost_ap->f(); // use the vptr of C::A: safe?

(此处使用volatile 以避免编译器传播类型知识)

如果对于C 的左值,对于从A 继承的虚函数,调用是通过C vptr 完成的,它也是C::A vptr,因为A 是“静态" C 的主要基础,那么代码应该可以工作,因为已经生成了一个从 CD 的 thunk。

实际上它似乎不适用于 GCC,但如果您在 C 中添加覆盖器:

struct C : virtual public A {
    int j; 

    virtual void f()  
    {
        std::cout << "C:f() \n";
    }    
};

之所以有效,是因为这样的具体功能在C::A的vtable的vtable中。

即使只有一个纯虚拟覆盖器:

struct C : virtual public A {
    int j; 
    virtual void f() = 0; 
};

D 中的具体覆盖器,它也可以工作:纯虚拟覆盖足以在C::A 的vtable 中有正确的条目。

测试代码:http://codepad.org/AzmN2Xeh

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-19
    • 1970-01-01
    • 1970-01-01
    • 2020-10-17
    • 1970-01-01
    • 2013-02-19
    相关资源
    最近更新 更多