我们知道,一个C++类如果带有virtual关键字的函数,那么,它就是一个虚类。虚类都有虚函数表。这个虚函数表真的存在吗?能摸得着、看得见吗?
的确是可以的。下面我们就展示一下如何看到C++类的虚函数表。请注意,我使用的系统是Ubutu 10.04, g++ 4.6.3。
下面我们先定义一个简单的纯虚类和它的实现类:
class BaseClassNoDeconstructor { public: virtual void func() = 0; }; class VirtualClassNoDeconstructor : public BaseClassNoDeconstructor{ public: virtual void func() { cout << "VirtualClassNoDeconstructor func\n"; } };
然后,针对类VirtualClassNoDeconstructor,我们定义两个等价的struct:
struct VirtualClassNoDeconstructorMember; struct VirtualClassNoDeconstructorVTable { void (*func)(VirtualClassNoDeconstructorMember*); }; struct VirtualClassNoDeconstructorMember { VirtualClassNoDeconstructorVTable * vtable; };
然后,我们用VirtualClassNoDeconstructorMember来调用它:
void dofuncNoDeconstructor(BaseClassNoDeconstructor* bcd) { cout <<"======= call by class no deconstructor by vtable: ===\n"; VirtualClassNoDeconstructorMember * pvcd = (VirtualClassNoDeconstructorMember*)bcd; pvcd->vtable->func(pvcd); cout <<"======== end =======\n"; }
想看下输出结果吗?
======= call by class no deconstructor by vtable: === VirtualClassNoDeconstructor func ======== end =======
我想不用多说什么了。
那么,对于一个有虚析构造的类,情况又是什么呢?首先,还是先看简单的类:
class BaseClass { public: virtual ~BaseClass() { } virtual void func() = 0; }; class VirtualClass : public BaseClass { public: virtual ~VirtualClass(){ cout << "Virtual Class destory"<<endl; } virtual void func() { cout << "Virtual Class : v="<<value<<endl; } VirtualClass(int v) : value(v) { }; private: int value; };
这两个类有虚的析构造,对应的等价struct则需要定义为:
struct VirtualClassVTable { void (*dector)(VirtualClassMember* self); void (*delete_obj)(VirtualClassMember* self); void (*func)(VirtualClassMember* self); }; struct VirtualClassMember { VirtualClassVTable* vtable; int value; };
看,多了两个函数:dector和delete_obj。先别着急,先看看测试代码和输出结果
void dofunc(BaseClass* bc) { cout <<"====== call class by vtable: ===\n"; VirtualClassMember *pvcm = (VirtualClassMember*)bc; cout <<"from member value="<< pvcm->value<<endl; pvcm->vtable->func(pvcm); pvcm->vtable->dector(pvcm); printf("VTable %p: func=%p, ~=%p\n",pvcm->vtable, pvcm->vtable->func, pvcm->vtable->dector); printf("pvcm->vtable->delete_obj=%p\n", pvcm->vtable->delete_obj); printf("pvcm->vtable->dector=%p\n", pvcm->vtable->dector); printf("pvcm->vtable->func=%p\n", pvcm->vtable->func); cout <<"======== end ======\n"; }
输出结果呢,则是
====== call class by vtable: === from member value=1234 Virtual Class : v=1234 Virtual Class destory VTable 0x401150: func=0x400970, ~=0x400ce2 pvcm->vtable->delete_obj=0x400d14 pvcm->vtable->dector=0x400ce2 pvcm->vtable->func=0x400970 ======== end ======
按照通常的做法,增加了一个析构造,应该增加一个函数才对,而实际上却增加了两个。其中一个就是真正的构造,而另外一个,是伴随他产生的一个删除函数:delete_obj。
这个函数是由编译器自动产生的。那么,他的作用是什么?为了搞清楚这个问题,我用objdump反编译代码,从其中摘抄了下面一段:
0000000000400d14 <_ZN9BaseClassD0Ev>: 400d14: 55 push %rbp 400d15: 48 89 e5 mov %rsp,%rbp 400d18: 48 83 ec 10 sub $0x10,%rsp 400d1c: 48 89 7d f8 mov %rdi,-0x8(%rbp) 400d20: 48 8b 45 f8 mov -0x8(%rbp),%rax 400d24: 48 89 c7 mov %rax,%rdi 400d27: e8 b6 ff ff ff callq 400ce2 <_ZN9BaseClassD1Ev> 400d2c: 48 8b 45 f8 mov -0x8(%rbp),%rax 400d30: 48 89 c7 mov %rax,%rdi 400d33: e8 d8 fb ff ff callq 400910 <[email protected]> 400d38: c9 leaveq 400d39: c3 retq
这一段汇编代码的地址正是delete_obj函数指向的地址400d14。请看400d27位置的callq指令,调用的地址是400ce2。这个地址是dector的地址,即析构造的地址。再请看400d33的callq指令。[email protected]表示什么意思呢?@plt表示这是一个引入了其他动态库的函数。_ZdlPv用c++filt转换后,就是名字"operator delete(void*)"。
在这里,也提醒下各位,当使用虚类的时候,一定要带上虚的析构造函数。否则在删除一个虚类对象时,很可能会发生内存的泄漏!