關於指針與類的內存分佈問題(問題思考來自《程序員面試寶典》)

引起思考的例子:

class A
{
public:
	int a_1,a_2;
	A() :a_1(1),a_2(2){}
	void func()
	{
		cout << a_1 << endl;
		cout << a_2 << endl;
	}
};

class B 
{
public:
	int b;
	B() :b(3){}
	void func()
	{
		cout << b << endl;
	}
};
int main()
{
	A a;
	B * pb = (B*)&a;
	pb->func();
	system("pause");
}

雖然是一個很有問題的代碼,但是需要預測此時程序的行爲。程序將輸出 : 1

解釋:

將 &a 強制類型轉換爲 B* ,因此在調用 pb->func() 的時候,編譯器根據 pb 的類型將調用 B::func()。A中有兩個成員 a_1,a_2。在A的內存佈局中,其偏移量分別爲0 與 1。B中有一個成員 b 。在 B 的內存佈局中,其偏移量爲 0。 在  B::func() 中,對 b 的訪問在彙編語言層而言是對於相對 this 指針所指地址的偏移量爲 0 的 int 成員的訪問。此函數調用在編譯時期不會出錯。而在運行時期,通過對 this 指針所指對象的偏移量爲 0 的地址訪問。導致此時訪問到了 A 中的 a_1 成員,該對象的此時的值爲 1。因此輸出爲 1。

但是這種問題其實沒什麼好糾結的。是視編譯器而定的內存佈局。假設編譯器對 A 對象的佈局中,讓 a_2 位於 a_1 的前方,則測試輸出的值則爲 2 了。測試一個真實的樣例:讓 A中有虛函數,則此時  A 的內存佈局中有 vptr,會使得上述分析失效。如:

class A
{
public:
	int a_1,a_2;
	A() :a_1(1),a_2(2){}
	void func()
	{
		cout << a_1 << endl;
		cout << a_2 << endl;
	}
	virtual void f()     //與上述的代碼完全相同,只是在A中增加了一個虛函數
	{
	}
};

此時,輸出的結果爲一個地址值、即爲 A::Vptr 的值(在VS2013中)。說明在VS中,虛函數指針放在類對象內存佈局的頭部。因此對偏移量爲 0 的訪問則訪問到了 A 的虛函數表的指針。若 B 中也有虛函數,則 A ,B 的佈局相對來說是一樣的。偏移量都包含了虛函數表的指針。則仍然符合上述分析。


一個思考:

類似上述問題,類中虛函數的調用也是與偏移量有關。因爲虛函數在虛函數表中存儲的就是偏移量。因此虛函數的調用可能會出現如下情況:

class A
{
public:
	int a_1,a_2;
	A() :a_1(1),a_2(2){}
	void func()
	{
		cout << "afunc" << endl;
		af();
	}
	virtual void af()
	{
		cout << "af" << endl;
	}
};

class B 
{
public:
	int b;
	B() :b(3){}
	void func()
	{
		cout << "bfunc" << endl;
		bf();          //對虛函數的調用轉爲對虛函數表中的偏移量對應的函數的調用
	}
	virtual void bf()
	{
		cout << "bf" << endl;
	}
};
int main()
{
	A a;
	B * pb = (B*)&a;
	pb->func();<span style="white-space:pre">			</span>//將會調用 B::func();
	system("pause");
}

輸出爲:

bfunc

af

解釋:
同理。pb->func() 調用的是 B::func(),因此輸出了 bfunc 。然而由於 bf() 是B中的虛函數,而虛函數的調用會轉爲針對 vptr 的偏移量的調用,此時偏移量爲 0 。但由於此時指針實際上指向的是 A 對象,因此 vptr 是A的 vptr 。同時 A 的 vptr 的偏移量爲 0 的函數爲 A::af(), 因此調用了 A::af()。並輸出 af


對於繼承的思考:

繼承的多態體現在函數成員多態。而數據成員不具有多態性

class A
{
public:
	int val;
	A() :val(1){}

	void f()
	{
		cout << "A" << endl;
		cout << val << endl;
		func();
	}
	virtual void func()
	{
		cout << "afunc" << endl;
	}
};

class B : public A
{
public:
	int val;
	B() :val(2){}
	void f()
	{
		cout << "B" << endl;
		cout << val << endl;
	}
	virtual void func()
	{
		cout << "bfunc" << endl;
	}
};


int main()
{
	B b;
	A * pa = &b;
	pa->f();
	system("pause");
}

輸出爲:
A

1

bfunc

解釋:

非虛函數沒有多態性,指針的靜態類型決定了調用的函數,因此調用A::f()。而在 A::f() 中,數據成員的訪問不具有多態性,因此 val 即爲 A::val。因此輸出 1。而虛函數 func 的調用則存在多態性,調用了 B::func();


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章