c++类的内存布局
?
by andydeng ? 2011 年 4 月 3 日 ? C++ ? 1 Comment
本文基本上是對于Stanley B.Lippman的Inside The C++ Object Model一書第一章第三章的概括,描述了c++類的內存布局情況.
c++的類的內存布局有如下規則:
1. Nonstatic data member 存放在Class Object中;
2. Static data member, static/nonstatic member function存放在class object之外.
3. 若類有virtural function, 則在class object 中增加virtual pointer(vptr)指向virtural function tabel(vtbl). vptr在類中的位置有兩種情況:1是在所有成員變量之后,這么做的好處是這個類能和c語言兼容.2是在最前面,這樣做的話,虛擬繼承等實現會方便一點,但是不能和c兼容.在前或后依賴編譯器實現.
4. nonstatic data member 若在同一個access section中, 則變量在內存中的順序保證和聲明中的順序一致(較晚出現的變量有較高的地址).而不同access section中的數據順序則沒有保證,依賴編譯器實現.
5. 內存對齊: 類的各個成員,第一個成員位于offset為0的位置,以后每個數據成員的偏移量必須是min(#pragma pack(), 這個數據成員自身的長度)的倍數
???????????? 數據成員自身對齊后, 類本身也要進行對齊, 對齊將按照min(#pragma pack(), 類中長度最大的成員)的倍數進行.
6. 如果類為空,編譯器會安插1byte的數據到類中,以確保類的每個實例都會有唯一的內存地址
7. 繼承后,子類的數據成員不會占用父類內存對齊用的空間. C++語言保證:"出現在derivd class 中的base class subobject有其完整的原樣性"
8. 如果類的繼承體系不是單一,而是多重繼承,但是不含虛擬繼承,那么有多少條繼承鏈,內存布局中就有多少個vptr.多個繼承鏈的位置,和繼承時的聲明順序一致.
9. 如果使用了虛擬繼承,則先將derived class的不變部分布局,然后再布局虛擬繼承的base class,而具體布局則有以下情況:
? 1. 使用Pointer Strategy, 每一個虛擬繼承的類,都有一個額外的指針指向base class
? 2. 使用Virtual table offset strategy, 不加入額外的指針指向base class,而是在vtbl的-1的offset內放置該類與虛擬繼承的基類之間的offset. 這樣的話運行時則可以通過derivedPointer + vptr[-1]得到.
? 3. 使用Virtual base class table. 這個是微軟的做法,不過書中并沒有具體描述怎么做,根據我的理解好像是在vtbl中加入一個指針指向base class.
如果采用2或3, 那么內存布局和8并不會有太大區別,就是virtual base class跑到最后面去了.
第9種情況異常復雜,建議看原書外加自己在多個編譯器上實踐實踐.
用實例來說說(32位機器)
規則1和2:
class A
{
public:
int a;
static char b;
void foo();
static void bar();
};
ASSERT(sizeof(A) == 4);
那么在每一個A對象內,只含有一個a,也就是,sizeof(A) == 4 .
而b,foo(),bar(),都不在class object之內,他們在內存中有唯一實體.
規則3
class B
{
public:
int a;
virtual void foo();
};
ASSERT(sizeof(B) == 8);
一個int和一個vptr,共8位
規則4,沒啥好說的,一般就算在不同的access section,都會按照一致的順序來聲明.但是要注意順序一致不代表連續.因為變量間可能會有一些bytes用于內存對齊.
規則5
class C
{
int a;
char b;
};
ASSERT(sizeof(C) == 8);
規則5有兩條子規則,第一條對這個例子沒用,經過第一條后C的大小還是5,可是第二條要求整個類要對齊,那么必須在char b后增加3bytes.
如果int和char的聲明順序反一下,那么為滿足第一條規則,類已經需要對齊成8了,已經是8那么第二條也滿足了.
規則6
class D
{};
ASSERT(sizeof(D) == 1);
這1byte是編譯器插進去的,如果不插的話,連續聲明D a,b;再取他們的地址,就會變成一樣的了.就無法分辨哪個變量是哪個了.
不過要注意的是,任何類繼承了D,只要里面有vptr或者任何一個變量,那么編譯器就不會在子類中加入這1 byte了.(這個是依賴于編譯器的,而不是標準規定.如果編譯器沒有去掉這1byte的話,那么就要內存對齊了.)
規則7
class E:public C
{
int c;
char d[2];
};
ASSERT(sizeof(E) == 16);
編譯器不會為了節省空間把E的成員插入到C為了內存對齊的而補出的空間中的.這道題我面試的時候被問過,我答16的時候面試官還認為錯了,太浪費空間了.但是這的確是唯一的正確解.
規則8
class F
{
int b;
virtual void bar();
};
class G:public B, public F
{
int c;
};
ASSERT(sizeof(G) == 20);
3個int,2個vptr,一共20
規則9
class H:virtual public B
{
int a;
};
class I:virtual public B
{
int b;
};
class J:public H, public I
{
int a;
};
ASSERT(sizeof(J) == 28);
4個int一共是16,BHI3個類都有各自的vptr,16+4*3==28.
這東西其實我一年半前就看書看到過,可惜實際編程中基本是用不到這種東西的,導致我忘了不少,面試的時候有幾個沒答出來,十分可惜,特意花一天時間重新啃了那書再總結總結加深記憶.另外真的要對自己說聲加油啊.
總結
- 上一篇: C++类内存结构布局
- 下一篇: C/C++中的常量指针与指针常量