你所不知道的C++ 之 C++虛類模型
阿新 • • 發佈:2019-02-19
我們知道,一個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 =======
我想不用多說什麼了。
那麼,對於一個有虛析構造的類,情況又是什麼呢?首先,還是先看簡單的類:
這兩個類有虛的析構造,對應的等價struct則需要定義為: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 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 <_ZdlPv@plt>
400d38: c9 leaveq
400d39: c3 retq
這一段彙編程式碼的地址正是delete_obj函式指向的地址400d14。請看400d27位置的callq指令,呼叫的地址是400ce2。這個地址是dector的地址,即析構造的地址。再請看400d33的callq指令。_ZdlPv@plt表示什麼意思呢?@plt表示這是一個引入了其他動態庫的函式。_ZdlPv用c++filt轉換後,就是名字"operator delete(void*)"。
在這裡,也提醒下各位,當使用虛類的時候,一定要帶上虛的析建構函式。否則在刪除一個虛類物件時,很可能會發生記憶體的洩漏!