本文通過分析記憶體布局來了解編譯器對類的記憶體布局的安排。分析方法為先貼出**,在給出相應記憶體的內容,最後給出分析結果。
以下執行環境為vs2005,不同編譯器實現可以不一樣(一般是因為c+標準並未要求)。具體可參見《深度探索c++物件模型》。
單一繼承
**如下:
class a
int _a;
};class b:public a
int _b;
};int _tmain()
對應物件a和b的記憶體區為:
0x0012ff38 cc cc cc cc ....
0x0012ff3c 00 00 00 00 ....物件b
0x0012ff40 11 00 00 00 ....
0x0012ff44 cc cc cc cc ....
0x0012ff48 cc cc cc cc ....
0x0012ff4c 00 00 00 00 ....物件a
0x0012ff50 cc cc cc cc ....
結論:1.執行時棧上的自動物件在定義時相鄰,但是記憶體位址不一定相鄰。
2.先宣告的變數占有高位址,後宣告的在低位址處。
3.對於物件b,其繼承而來的部分在上,類b獨有的部分在下。這樣可以和c的結構體相容,但是c++標準並未強制規定必須如此布局(但一般編譯器都是這麼做的)。
多繼承
**如下:
class a
int _a;
};class b
int _b;
};class c:public a, public b
int _c;
};int _tmain()
物件c的記憶體區為:
0x0012ff28 cc cc cc cc ....
0x0012ff2c 00 00 00 00 ....物件c
0x0012ff30 11 00 00 00 ....
0x0012ff34 22 00 00 00 "...
0x0012ff38 cc cc cc cc ....
結論:1.物件按宣告的繼承順序排列,如下面的物件c布局為:繼承自類a的成員-》繼承自類b的成員-》類c自己定義的成員。若改為下面的宣告:
class c:public b,public a
int _a;
virtual void print(){}
};class c:public a
int _c;
};int _tmain()
物件c的記憶體區為:
0x0012ff30 cc cc cc cc ....
0x0012ff34 78 3d 48 00 x=h.物件c
0x0012ff38 00 00 00 00 ....
0x0012ff3c 22 00 00 00 "...
0x0012ff40 cc cc cc cc ....
0x0012ff44 cc cc cc cc ....
0x0012ff48 70 3d 48 00 p=h.物件a
0x0012ff4c 00 00 00 00 ....
0x0012ff50 cc cc cc cc ....
結論:1.除錯視窗可以看到物件a有兩個成員:_c和__vfptr。很明顯後者為編譯器自動增加用來支援虛函式的(後面用__vfptr表示該4位元組)。你可以試試自己新增個名稱為__vfptr的變數,看看有啥效果。(記憶體有它的位置,但你卻沒法引用它,因為編譯器將該符號繫結到特定的位置了(該處是物件第乙個位元組)。
2.類中僅有乙個__vfptr。所以每個類僅有乙個虛函式表,表可以理解成乙個void*型的陣列,其中每一項為乙個虛函式位址。這個陣列大小是固定的,一般取決於虛函式的個數,位置則按宣告時的位置來定,子類的虛函式表中,基類所定義的虛函式在該錶的上面(索引較小處)。一次排列。
3.繼承之後,__vfptr也只有乙個,由此我們想到,當用子類初始化父類時,__vfptr需要編譯器新增指令來維護其值。例如:
a=c;//此時,a獲得c繼承值a的部分,但不包括__vfptr的值。有興趣的可以看看該語句的彙編**。
下面分析下這種情況下得虛函式表,**如下:
class a
int _a;
virtual void printa(){}
};class c:public a
int _c;
virtual void printc(){}
};int _tmain()
a類:
0x00483d70 13 b1 42 00 ..b.下劃線數所表示的位址處是一條跳轉指令,跳到printa。
0x00483d74 00 00 00 00 ....
c類:0x00483d7c 13 b1 42 00 ..b.明顯,位址一樣,且在虛函式表中索引一樣,如果重寫了printa,這裡的位址就會變。
0x00483d80 af b5 42 00 ..b.這個項表示c中新增的虛函式printc,同樣是跳轉指令
0x00483d84 00 00 00 00 ....
class a
int _a;
//virtual void printa(){}
};class c:virtual public a
int _c;
//virtual void printb(){}
};int _tmain()
0x0012ff34 cc cc cc cc ....
0x0012ff38 6c 3d 48 00 l=h.用於支援虛基類的成員
0x0012ff3c 22 00 00 00 "...
0x0012ff40 00 00 00 00 ....
0x0012ff44 cc cc cc cc ....
0x0012ff48 cc cc cc cc ....
0x0012ff4c 00 00 00 00 ....
0x0012ff50 cc cc cc cc ....
那上面的4位元組是如何支援虛基類的了,下面看乙個賦值操作:
a* pa= null;
pa = &c;//這一條
上面的語句翻譯成彙編結果為:
pa = &c;
0042d705 lea eax,[c] c的位址給eax
0042d708 test eax,eax 這個其實是用來測試c的位址是不是為0,這裡肯定不會為0,但是如果用c的指標來賦給pa就不一定了
0042d70a jne wmain+78h (42d718h)
0042d70c mov dword ptr [ebp-0f0h],0 c的位址為0
0042d716 jmp wmain+88h (42d728h)
0042d718 mov ecx,dword ptr [c] 這裡就是上面紅色地方的內容了,完成後ecx=0x00483d6c
0042d71b mov edx,dword ptr [ecx+4] 記憶體中的內容如下,完成後edx=8,可想而知這8應該是c類獨有部分的大小(4位元組)和支撐部分的大小(4位元組,紅色區域)
0042d71e lea eax,c[edx] 計算c中a子物件的偏移
0042d722 mov dword ptr [ebp-0f0h],eax
0042d728 mov ecx,dword ptr [ebp-0f0h]
0042d72e mov dword ptr [pa],ecx
0x00483d68 d8 71 06 00 .q..
0x00483d6c 00 00 00 00 ....
0x00483d70
08 00 00 00
....
0x00483d74 00 00 00 00 ....
因此,支撐部分存放的有該類虛基類部分在類中的偏移。我猜有兩個虛基類時,這裡會有2項。我加了個虛基類後支撐部分變成下面部分:
0x00483d68 d8 71 06 00 .q..
0x00483d6c 00 00 00 00 ....
0x00483d7008 00 00 00....
0x00483d740c 00 00 00....
0x00483d78 00 00 00 00 ....
0x00483d7c 55 6e 6b 6e unkn
如果同時還有虛函式了。。
C 物件記憶體布局
好文要記下來 上 下 玄機逸士系列 補充一點,兩個博文裡面都沒有給出虛基類表中的第一項的解釋,其實第一項就是vbptr到自己類物件位址的偏移量。若沒有虛函式,也就是沒有vfptr,偏移量為0,若有,就為 4 vfptr 在 vbptr之前,所以是 4 玄機逸士的結論 vc 6 其一,只要涉及到虛基類...
C 物件記憶體布局
單一的一般繼承 可見以下幾個方面 1.虛函式表在最前面的位置 2.成員變數根據其繼承和宣告的順序一次放在後面 3.在單一繼承中,被 overwrite 的虛函式在虛函式表中得到更新 多重繼承 我們可以看到 1.每個父類都有自己的虛函式表 2.子類的成員函式被放在第乙個父類的表中 3.記憶體布局中,父...
C 記憶體物件布局
本章主要介紹了c 類中成員變數 函式物件的在記憶體中布局.當c 類中不包含virtual機制類的函式時,內部nostatic member被包含在每乙個class object之中,就想c struct一樣,而member function雖然含在class宣告之內,卻不出現在object之中,每乙...