在上篇博文中,類中的首地址多出來四個字節
以下將驗證多出來的的四個字節究竟是不是虛函數表。
————————————下面是正文——————————————
在驗證之前,首先來補充一些知識:函數指針
根據我以前寫的一篇《【c語言】帶你真正走進指針的世界——函數指針》中可以得知:函數名就是函數的指針,代表了函數的真實地址。以下爲函數指針的簡單運用:
#include <iostream>
void test()
{
std::cout<<"THE TEST IS ONE!"<<std::endl;
}
void main()
{
void (*p)();
p = test;
p();
}
編譯出來的結果可以清晰的看到使用函數指針可以調用所指的函數:
而在本次的博文中,將引用函數指針來證實多出來的四個字節是否爲虛函數表。首先,我們假設如果多出來的四個字節真的是虛函數表的話,那麼這四個字節就是一個地址,而這個地址裏所儲存的內容就是第一個虛函數的地址。
圖上爲多出來的四個字節(假設爲虛函數表的地址)
在地址欄輸入 432028 ,可以看到地址432028 中存有數據 401064
瞭解以上知識後,我們首先要接觸到的第一個問題是如何將地址 432028 中的地址 401064 取出來呢?在提取第一個虛函數的地址 401064 之前,我們需要將虛函數表的地址 43202c 先提取出來,順着虛函數表的地址去找虛函數的地址。首先,我們可以嘗試輸出類名,觀察輸出的結果是什麼東西:
printf("%x\n",b);
編譯的結果可以看出,是多出來的四個字節:
到了這裏,就有了一個疑問:那我可不可以直接用這個賦值給函數指針,然後進行調用呢?答案是不行的,這個是虛函數表的地址,並不是虛函數的地址,我們需要的是虛函數的地址賦值給函數指針,而且,使用類名輸出的結果雖然只有一個,然而實際上代表的是整個類,只是類名代表了類的首地址,所以直接拿出來用也就相當於將整個類賦值給函數指針,並不是我們需要的結果。
所以,我們需要的是取出類中的前四個字節,所以,我們需要一步一步地將類中所儲存的 前四個字節 提取出來,首先,我們需要對類名進行賦值(加星)處理:
printf("%x\n",&b);
編譯的結果可以看出,輸出爲類的首地址:
然後,我們需要的是類中所儲存的前四個字節,所以,我們進行一個強制轉化類型,使得輸出只取四個字節:
printf("%x\n",(int*)&b);
編譯的結果可以看到,雖然和上一個結果沒有什麼區別,實際上類型已經變成了一個 int* 類型的指針,指針大小爲 四個字節:
最後,如果我們要將一個指針所存儲的內容取出來的話需要進行什麼操作呢?是的,進行降星就可以了:
printf("%x\n",*(int*)&b);
如此這般,最後得到的結果雖然和第一個圖片的結果相一致,但實際上差距卻天差地別,還是不懂的同學可以多看幾次我上面的解釋~
有了上面的基礎,我們也就很容易地可以得到地址 432020 裏面所儲存的第一個地址(也就是第一個虛函數)了,也就是重複上面的步驟而已:
printf("%x\n",*(int*)*(int*)&b);
不明白的可以看下面的解釋圖:
後面,我們就可以直接使用指針函數,將上面 *(int*)*(int*)&b 賦值給函數指針,然後通過函數指針進行調用:
#include <iostream>
class base
{
public:
base()
{
x = 1;
y = 2;
}
int x,y;
virtual function1()
{
std::cout<<"The test is one!"<<std::endl;
}
};
void main()
{
base b;
int (*p)();
p = (int(*)())(*(int*)(*(int*)&b));
p();
}
編譯出來的結果可以看到,打印出來虛函數中的字符:
由此可以證明,類中多出來的四個字節,確實就是虛函數表的地址。
如果想要測試兩個或者以上的虛函數,可以將上述代碼中的 (int(*)())(*(int*)(*(int*)&b)+N) 來實現函數的調用。