這兩天一直在研究對象模型的問題,發現這個問題確實不好理解,但通過反彙編,對於彙編下,數據段,代碼段有了一定的認識。
經過兩天的研究測試發現當一個類被定義了之後,數據和函數是分離的,函數是放在斷碼段的,函數名標示函數的起始地址,往下就是函數內部的指令
當創建一個類的object時,棧上開闢空間來容納其中的數據變量,然後調用構造函數來初始化數據成員,如何調用構造函數我至今也不太清楚。。。
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A's constructor" << endl;
}//標記3
A(int i):a(i){}
A(const A &orig):a(orig.a)
{
}
A& operator = (const A &orig)
{
a = orig.a;
return *this;
}
virtual ~A(){}
private:
int a;
};
class B:publicA
{
public:
B()
{
cout << "B's constructor" << endl;
}//標記2
B(int i,int x):A(i),b(x){}
B(const B &orig):A(orig),b(orig.b)
{
}
B& operator = (const B &orig)
{
A::operator = (orig);
b = orig.b;
return *this;
}
~B(){}
private:
int b;
};
int main()
{
A *p = new B;//
return 0;
}
對於上面這段代碼通過反彙編發現
0041C94C A()
0041C9D4 ~A()
0041C9FC B()
0041CAEC ~B()
說明代碼段中,基類成員函數的位置處於上層,派生類在下層,這和C++繼承體系裏的scope吻合不是巧然,這說明編譯器在查找函數的時候是由下層地址往上層地址去找的。
B類對象模型如下:
------ <<--- this -------- ----------
|vpt | ------>> | ~B地址 | <<-----虛表 | A | <<-----代碼段
------ -------- ~A
|a | | ... |
------ B
|b | | ~B |
------ ----------
上圖是我推測的B類對象的模型,首先創建對象的時候棧上分配一段空間裝數據成員,因爲有虛函數所以附帶一個vpt,指向虛函數表的首地址,這個是B類的虛函數表,這裏只有一個虛析構函數,在B類的虛函數表裏把A的虛析構函數替換了。
分配空間之後,調用構造函數來初始化變量,這個時候就跳到代碼段B()的位置,然後又向上層地址找到A()的位置,執行裏面的指令(給a賦值),然後回到B(),執行裏面的指令(給b賦值)
析構的時候,這裏析構函數是虛的,然後就根據確切類型,也就是B類,去B類的虛表中找,結果找到了~B(),然後跳到代碼段~B()執行完裏面的指令在向上層地址找到~A()
如果A的析構函數不是虛的,那麼對象模型就完全不同
------ <<--- this --------
|a | | A | <<-----代碼段
------ ~A
|b | | ... |
------ B
| ~B |
--------
這裏就沒有所謂的虛表了,析構的時候,虛函數不是虛的,調用就不是通過虛表了,這個時候根據靜態綁定,這個時候就直接跳到了代碼段~A(),然後執行裏面的指令,這個時候編譯器再不會去找~B()了,因爲它在下層地址,結果造成了所謂的內存泄漏
這些就是我的理解了,就當拋磚引玉吧...