C++ 虛擬函式表指標以及虛擬函式指標的確定
【摘要】
很多教材上都有介紹到虛指標、虛擬函式與虛擬函式表,有的說類物件共享一個虛擬函式表,有的說,一個類物件擁有一個虛擬函式表;還有的說,無論使用者聲明瞭多少個類物件,但是,這個VTABLE虛擬函式表只有一個;也有的在說,每個具有虛擬函式的類的物件裡面都有一個VPTR虛擬函式指標,這個指標指向VTABLE的首地址,每個類的物件都有這麼一種指標。今天,我們就來解決這個問題,同一個類的不同物件,是不是擁有“相同”的虛擬函式表,這個相同是物理上的相同(記憶體地址)還是邏輯上的相同(資料結構)。本文現詳述如下!
【正文】
虛指標:每個含有虛方法(虛擬函式)物件裡有虛表指標,指向虛表。
虛擬函式表:虛擬函式表是順序存放虛擬函式地址的,虛表是順序表,表裡存放了虛擬函式的地址。
C++的編譯器應該是保證虛擬函式表的指標存在於物件例項中最前面的位置(這是為了保證取到虛擬函式表的有最高的效能——如果有多層繼承或是多重繼承的情況下)。 這意味著我們通過物件例項的地址得到這張虛擬函式表,然後就可以遍歷其中函式指標,並呼叫相應的函式。
【程式碼示例】
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
void h() { cout << "Base::h" << endl; }
};
typedef void(*Fun)(void); //函式指標
int main()
{
Base b;
Base c;
// 這裡指標操作比較混亂,在此稍微解析下:
// *****printf("虛表地址:%p\n", *(int *)&b); 解析*****:
// 1.&b代表物件b的起始地址
// 2.(int *)&b 強轉成int *型別,為了後面取b物件的前四個位元組,前四個位元組是虛表指標
// 3.*(int *)&b 取前四個位元組,即vptr虛表地址
//
// *****printf("第一個虛擬函式地址:%p\n", *(int *)*(int *)&b);*****:
// 根據上面的解析我們知道*(int *)&b是vptr,即虛表指標.並且虛表是存放虛擬函式指標的
// 所以虛表中每個元素(虛擬函式指標)在32位編譯器下是4個位元組,因此(int *)*(int *)&b
// 這樣強轉後為了後面的取四個位元組.所以*(int *)*(int *)&b就是虛表的第一個元素.
// 即f()的地址.
// 那麼接下來的取第二個虛擬函式地址也就依次類推. 始終記著vptr指向的是一塊記憶體,
// 這塊記憶體存放著虛擬函式地址,這塊記憶體就是我們所說的虛表.
//
printf("虛表地址:%p\n", *(int*)(&b));
printf("第一個虛擬函式地址:%p\n", * (int*)*(int*)(&b) );
printf("第二個虛擬函式地址:%p\n", *((int*)*(int*)(&b)+2));
Fun pfun = (Fun)*((int*)*(int*)(&b)); //vitural f();
printf("f():%p\n", pfun);
pfun();
pfun = (Fun)(*((int*)*(int*)(&b) + 2)); //vitural g();
printf("g():%p\n", pfun);
pfun();
printf("虛表地址:%p\n", *(int*)(&c));
printf("第一個虛擬函式地址:%p\n", * (int*)*(int*)(&c));
printf("第二個虛擬函式地址:%p\n", *((int*)*(int*)(&c)+2));
pfun = (Fun)*((int*)*(int*)(&c)); //vitural f();
printf("f():%p\n", pfun);
pfun();
pfun = (Fun)(*((int*)*(int*)(&c) + 2)); //vitural g();
printf("g():%p\n", pfun);
pfun();
}
程式碼是在Ubuntu16.04 64bit下跑的,在32bit下程式要把2修改成1,這個是由於32bit和64bit的區別。
另外本程式碼在win10+VS2015下沒法跑
如果Base訪問資料成員,而我們通過獲取函式指標來訪問函式,這個會出現SegmentFault 錯誤,因為我們通過強制轉換的函式指標訪問資料成員的時候,函式是不找到this指標的,所以會報錯,這個建議和this指標到底存放到哪裡一起學習。
【結論】
不同物件虛擬函式表表中元素是相等的,邏輯上是一樣的,存放的都是類中虛擬函式的地址;
不同物件虛擬函式表的記憶體地址是一樣的,也即所有物件公共一個表
另外這個C++ 虛擬函式表解析 是存在問題的,關於取虛擬函式表的地址,它是按照物件的地址,也即this指標的地址做得,這個是不對的。