天天看點

C++虛函數、虛函數表位址

總結自:https://blog.csdn.net/haoel/article/details/1948051/

https://blog.csdn.net/qianghaohao/article/details/51356718

編譯器:VS2017

一、虛函數

當我們使用基類的引用或者指針調用一個虛成員函數時會執行動态綁定,直到運作時才知道到底調用了哪個版本的虛函數,被調用的函數是與綁定到指針或者引用上的對象的動态類型相比對的那一個。所有虛函數都必須有定義。虛函數的作用主要是實作多态機制。

using namespace std;

class Base {
public:
	virtual void funA() { cout << "funA of Base" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void funA() { cout << "funA of Derive" << endl; }
};

void display(Base* ptr) {
	ptr->funA();
}
int main()
{
	Derive d;
	Base b;
	Base* ptr = &b;
	display(ptr);
        ptr = &d;
        display(ptr);
	return 0;
}
           

上面輸出結果為 funA of Base 和 funA of Derive;函數display()接受一個基類指針參數,通過指針調用虛成員函數funA(),當基類指針指向一個基類對象執行個體時,調用的是基類的成員函數funA();當基類指針指向一個派生類對象執行個體時,調用的是派生類的成員函數funA()。如果funA()為非虛函數,則上面程式中display()調用的函數都為基類的funA(),因為基類指針隻能通路調用基類的成員,即使這個基類指針指向的是一個子類的對象執行個體。

二、虛函數表

虛函數是通過虛函數表實作的,虛函數表實際上是一個儲存類中虛函數位址的數組。

當類中存在虛函數時,這個類的對象執行個體就會有一個指向虛函數表的指針,同屬于一個類的對象共享虛函數表,在C++的标準規格說明書中說到,編譯器必需要保證虛函數表的指針存在于對象執行個體中最前面的位置(這是為了保證正确取到虛函數的偏移量)。 這意味着我們通過對象執行個體的位址得到這張虛函數表,然後就可以周遊其中函數指針,并調用相應的函數。

當通過指針調用類中的虛函數時,會根據指針所指對象的虛函數表位址通路虛函數表,找到相應的虛函數。

using namespace std;

class A {
public:
	void funA() { cout << "funA of Base" << endl; }
	void funB() { cout << "funB of Base" << endl; }
	void funC() { cout << "funC of Base" << endl; }
private:
	int a;
};
class Base {
public:
	virtual void funA() { cout << "funA of Base" << endl; }
	virtual void funB() { cout << "funB of Base" << endl; }
	virtual void funC() { cout << "funC of Base" << endl; }
private:
	int a;
};
int main()
{
	A a;
	Base b;
	cout << "A:" << sizeof(a) << endl;
	cout << "Base:" << sizeof(b) << endl;
	return 0;
}
           

程式輸出“A:4”和“Base: 8”,A對象比Base對象小的4個位元組存儲的就是虛函數表的位址。

下面的程式是通過虛函數表調用虛函數

using namespace std;

class Base {
public:
	virtual void funA() { cout << "funA of Base" << endl; }
	virtual void funB() { cout << "funB of Base" << endl; }
	virtual void funC() { cout << "funC of Base" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void funA() { cout << "funA of Derive" << endl; }
	virtual void funB() { cout << "funB of Derive" << endl; }
	virtual void funC() { cout << "funC of Derive" << endl; }
};

int main()
{
	Derive d;
	Base b;
	typedef void(*Fun)();
	Fun fA = (Fun)*((int*)*(int*)(&b));
        Fun fC = (Fun)*((int*)*(int*)(&b)+2);
        Fun f = (Fun)*((int*)*(int*)(&d));
	fA();
        fC();
	return 0;
}
           

輸出結果為"funA of Base" 、"funC of Base"、"funA of Derive"。即通過函數指針調用虛函數。

說明:

VS2017中虛函數表指針在對象中占四個位元組,虛函數指針占四個位元組;

(int*)(&b)指向對象執行個體的int類型指針;

*(int*)(&b)就是虛函數表的位址,int類型占4個位元組,解引用int類型指針讀取位址前四個位元組,(int*)*(int*)(&b)轉換為int類型的指針;

(int*)*((int*)*(int*)(&b))為虛函數表第一個虛函數的位址;

(int*)*(int*)(&b)與*(int*)(&b)指向的位置是一樣的,(int*)*(int*)*(int*)(&b)與*(int*)*(int*)(&b)指向的位置也是一樣的。

暫時這些,後續再補充修改g++下的情況。

繼續閱讀