Itanium C ++ ABI主要虚拟基础

时间:2017-06-08 10:35:43

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

我正在阅读关于如何选择主要基地的here

  

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

     

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

     

湾如果C具有动态基类,则尝试选择主基类B.它是第一个(以直接基类顺序排列)非虚拟动态基类(如果存在)。否则,它是一个几乎为空的虚拟基类,是(预订)继承图顺序中的第一个,如果存在则不是间接主基类,或者如果它们都是间接原色,则只是第一个...&#34 ;

在出现这种纠正之后:

  

"上面的案例(2b)现在被认为是设计中的错误。使用第一个间接主基类作为派生类' primary base不会保存对象中的任何空间,并且会在基类虚拟表的附加副本中导致一些虚函数指针的重复。

     

好处是使用派生类虚拟指针作为基类虚拟指针通常会节省负载,并且调用其虚函数时不需要调整此指针。

     

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

     

struct A {virtual void f(); };

     

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

     

struct C:virtual public A {int j; };

     

struct D:public B,public C {};

     

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

请你用一个例子来解释一下这是什么意思:" 删除那个要求不是一个好主意,因为那时候不再有一种方法可以用它们跳转的函数发出所有的thunk至"

此外,什么是第三方thunk

我不明白引用的例子试图显示的内容。

1 个答案:

答案 0 :(得分:0)

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但可见的A上面有两个字。它是一个幽灵A

(C::A)_vptr是vtable的vptr,其中C的布局vtable(也是A的布局vtable),但C子对象{{1}最终不是主要基础:A子对象失去了托管C基类的权限。很明显,通过A虚拟调用虚拟函数(C::A)_vptr(只有一个:A)需要A::f() ajustement,thunk" {{ 1}}"接收指向this的指针,并将其调整为C::A::f()类型的真实not(base_subobject),即(示例中的上述两个字)。 (或者如果base_subobject中有一个覆盖者,那么A对象位于完全相同的地址,示例中会有两个字。)

所以给出了这些定义:

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 vptr完成的,也是A vptr因为C是一个&#34;静态&#34; C::A的主要基础,然后代码应该有效,因为已经生成了从AC的thunk。

在实践中,它似乎不适用于GCC,但如果您在C中添加了一个覆盖:

D

它起作用,因为这样的具体功能在vtable C的vtable中。

即使只使用纯虚拟覆盖:

struct C : virtual public A {
    int j; 

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

C::A中的具体覆盖,它也有效:纯虚拟覆盖足以在struct C : virtual public A { int j; virtual void f() = 0; }; 的vtable中有适当的条目。

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