簡述虛表
虛表是記錄本類中所有虛函數地址的一個表格。
虛表的內存結構
如下,我們設計了一個類,存在兩個虛函數。
class A
{
public:
virtual void fun1() {
cout << "a" << endl;
}
virtual void fun2() {
cout << "b" << endl;
}
};
通過A實例化a,再看看a的內存結構。_vfptr就是虛表!!!可以把它看成存放了void*型對象的數組。 而_vfptr的兩個成員分別代表fun1函數的指針和fun2函數的指針。這也正好驗證了虛表就是記錄本類中所有虛函數地址的一個表格。
獲取虛表
經測試,通過如下代碼可以獲取虛表。
A a;
int *vptr = (int *)(*(int*)(&a));
再看內存監視信息,我們發現vptr的地址和_vfptr的地址一樣,測試通過-v-
通過虛表調用虛函數
看上圖,我們發現vptr[0]的值與虛表中第一個成員的值相等。那我麼可以通過吧vptr[0]轉換成fun1函數類型,來達到不可告人的目的。
typedef void (*Fun)(void);
Fun f1 = (Fun)vptr[0];
f1();
調用後,控制檯打印“a”,象徵着我們嘗試成功。同理,我們也可以通過vptr[1]來調用fun2函數。
虛表中虛函數的排列順序
通過上圖,我們很容易就可以看出,是按函數的聲明順序排列的。雖然簡單,但還是請記住了。不要一不小心調用了虛析構函數,程序直接崩潰。
繼承中覆蓋虛函數
我們再設計一個AA類,繼承A,並且在A中實現fun1()函數。
class AA : public A
{
public:
virtual void fun1() {
cout << "aa" << endl;
}
};
通過AA實例化aa,再看看aa的內存結構。
發現沒有,AA::fun1。我們只實現了fun1(),虛表中的fun1函數使用的是AA類中的實現,而fun2函數使用的時A類中的實現。有沒有隱隱約約嗅到動態綁定的味道?
結語
看完本篇文章,是不是對神祕莫測的虛表有了更一步的認識。
這裏只是從內存層面講解了虛表,欲瞭解更加透徹,請查閱“繼承”,“動態綁定”等知識。