可以存在空虚拟表吗?

时间:2011-10-21 10:11:35

标签: c++ virtual vtable

#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () {}
};

class Y : public Z
{
public:
    int a;
};

int main() 
{
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
} 

因为Y继承了Z,所以它也会有一个虚拟表。精细。但是,它没有任何虚函数,那么Y的虚拟表的内容是什么? 它会是空的吗?

4 个答案:

答案 0 :(得分:4)

这完全取决于编译器。当我强制YZ实例化时,g++ 4.4.5会为Y生成两个不同的虚拟表和Z具有相同的大小。

两个表都指向相同的x(),但指向不同的typeinfo结构:

;=== Z's virtual table ===
_ZTV1Z:
        .quad   0
        .quad   _ZTI1Z     ; Z's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Z:
        ; Z's type info (omitted for brevity)

;=== Y's virtual table ===
_ZTV1Y:
        .quad   0
        .quad   _ZTI1Y     ; Y's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Y:
        ; Y's type info (omitted for brevity)

答案 1 :(得分:3)

在您发布的示例中,GCC默认会完全优化vtable。因为它只有一个翻译单元,所以一切都是可见的。这是可能的。

我将你的例子改为:

#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () const {}
};

class Y : public Z
{
public:
    int a;
};

int main()
{
    Y y;
    const Z& z1=y;
    const Z& z2=Z();
    z1.x(),z2.x();
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
}

在这种情况下,在输出中生成vtable:

nm a.out|c++filt|grep -i vtable
08048880 V vtable for Y
08048890 V vtable for Z
0804a040 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3
0804a120 V vtable for __cxxabiv1::__si_class_type_info@@CXXABI_1.3

如果我们使用-S生成程序集,那么我们可以在我的系统上找到构造函数(分别为_ZN1ZC2Ev_ZN1YC2Ev。这些工作负责设置vtable(_ZTV1Z_ZTV1Y):

Z的构造函数:

_ZN1ZC2Ev:
.LFB970:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        movl    8(%ebp), %eax
        movl    $_ZTV1Z+8, (%eax)
        popl    %ebp
        .cfi_def_cfa 4, 4
        .cfi_restore 5
        ret

Y

_ZN1YC2Ev:
.LFB972:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        call    _ZN1ZC2Ev
        movl    8(%ebp), %eax
        movl    $_ZTV1Y+8, (%eax)
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret

这里有趣的是,在两个构造函数中放入vtable的内容基本相同。

答案 2 :(得分:1)

在通用编译器实现中,Y的虚拟表将具有与Z

相同的条目

答案 3 :(得分:0)

其他答案中已添加了血腥细节,但从非常高的角度来看,您必须认为派生类型Y确实拥有Z中所有继承的虚函数,它只是没有为其中任何一个提供覆盖(嗯,对于单个覆盖)。

整个虚拟功能表的想法是,从该基础派生的所有类型将具有兼容的表。当编译器需要找到要调用的虚方法的特定实现时,它知道它可以依赖于存在的表,并且在表的元素中是指向 final-overrider的指针该特定对象的方法,即使 final-overrider 碰巧是第一个也是唯一的覆盖者,按照你的例子。