前言
在我寫下這篇文章的時候,是2012年的春節。此刻已經深夜了,外面鞭炮聲震耳欲聾,想起往年的這個時候,現在該是和家裡人聚在一起看春晚的。而今已時過境遷。雖然如此,但我從未感到孤獨或什麼的,甚至連春晚都懶得看了。。 怪也就怪自己這幾天突然開始深度迷戀《inside c++ object model》,看到最入迷的時候,我甚至感覺自己的呼吸都停止了,真是一本不可多得的好書啊,呵呵。 題外話說多了,開始入正題。
對於C++的對象布局,早在以前看《effective c++》的時候,Scott Meyers在討論“為多態基類聲明虛析構函數”條款7)中如是說道:“無端地將所有class臼的析構函數聲明為virtual ,就像從未聲明它們為virtual 一樣,都是錯誤的。許多人的心得是:只有當class 內含至少一個virtual 函數,才為它聲明virtual 析構函數”。對於這點,當時或多或少能理解個大概,因為在以前看侯捷的《深入淺出MFC》的時候,作者對對象的虛函數表也略作了討論,雖不是很深入的探討,但也足以讓這樣我一個當時對C++對象內部布局毫無理解的小白對虛函數表的外觀有個大體認識。而今看的《inside c++ object model》也為侯捷所譯,而且對原版書籍中的很多錯誤都進行了詳細注解,不得不為這位台灣大師的資深水平及其多國外經典翻譯所作出的貢獻而深感敬佩。。不知什麼時候才會達到侯捷的水平,至少現在,我在努力。 —— stay hungry,stay foolish!
單一繼承後的對象布局
對於單個類的對象布局,如果其不含虛函數,那麼其與一個普通的c-struct布局類似。對於含有虛函數的類,還得考慮虛函數表vptr所帶來的開銷。而對於單一繼承後的對象布局呢?來看看如下代碼:
- #include <iostream>
- using namespace std;
- #include <iostream>
- using namespace std;
- class BaseClass
- {
- protected:
- int m_nValue;
- char m_chValue;
- };
- class DerivedClass1:public BaseClass
- {
- protected:
- char m_chValue1;
- };
- class DerivedClass2:public DerivedClass1
- {
- public:
- //virtual void VFunction(){}
- protected:
- char m_chValue2;
- };
- int main(int *argc,char **argv)
- {
- DerivedClass2 DerivedObject;
- cout<<"size of DerivedObject is: "<<sizeof(DerivedObject)<<endl;
- return 0;
- }
這段代碼在VS2008中的測試結果為:“size of DerivedObject is: 16”,DerivedClass的內存布局並非直接將所有基類成員先復制到自身然後再初始化,而是將基類以一種單一對象的方式初始化,然後初始化自身的nonstatic data member期間當然要遵循內存對齊原則)。這種布局方式類似於類或結構中存在委托對象的布局,試著將DerivedClass2改為如下:
- class DerivedClass2
- {
- public:
- //virtual void VFunction(){}
- protected:
- DerivedClass1 derived1;
- char m_chValue2;
- };
那麼得出的結果也會一樣。
加上虛繼承及多承繼承之後的對象布局
對於虛繼承之後的對象布局,來看看原書中一段有趣的代碼:
- #include <iostream>
- using namespace std;
- class BaseClass{};
- class DerivedClassA:virtual public BaseClass
- {
- public:
- //virtual void VFunction(){}
- };
- class DerivedClassB:virtual public BaseClass{};
- class DerivedClass:public DerivedClassA,public DerivedClassB{};
- int main()
- {
- BaseClass baseClass;
- DerivedClassA derivedClassA;
- DerivedClassB derivedClassB;
- DerivedClass derivedClass;
- cout<<"size of baseClass is: "<<sizeof(baseClass)<<endl;
- cout<<"size of direvedClassA is: "<<sizeof(derivedClassA)<<endl;
- cout<<"size of direvedClassB is: "<<sizeof(derivedClassB)<<endl;
- cout<<"size of direvedClass is: "<<sizeof(derivedClass)<<endl;
- return 0;
- }
在VS2008中得出的結果是:
size of baseClass is: 1
size of direvedClassA is: 4
size of direvedClassB is: 4
size of direvedClass is: 8
有人在G++編譯器下測試的結果為1,4,4,4。看來MS在編譯優化方面還沒有G++走的快。或許也僅僅是這一點吧。
以前只知道空struct 或 class的大小為1,肯定不是0,而一直不知其原因,直至現在看了Lippman大牛的著作,才一解心中之惑。對於BaseClass對象的布局,如果它裡面什麼都不存放,那麼定義兩個baseClass的對象,形式上看來它們應該是完全獨立的對象,而它們在內存中的地址值或許會一樣,為了避免這一點,編譯器在編譯時候為空class或struct就增加一個char(1byte),以使得不同對象有兩個相對獨立的地址值。
derivedClassA和derivedClassB因為有virtual base class,其對象中會存儲一個隱含的vbtptr來指向一個virtual base class table,這種表類似於virtual function table,只是virtual function table中存儲的是虛函數地址,而virtual base class table中存儲的是當前類的所有vitrual base的對象地址。vbtptr的初始化和析構工作如同普通nonstatc data member一樣,故而對象大小為4,而derivedClass繼承兩個vtptr至少vs2008編譯器肯定如此,g++編譯器對於相同的vtptr進行優化後,只存在一個vtptr)。大小為8也就不足為怪了。
如果去掉DerivedClassA中的virtual function的注釋,得出的結果會是:
size of baseClass is: 1
size of direvedClassA is: 8
size of direvedClassB is: 4
size of direvedClass is: 12
在direvedClassA的對象中又會增加一個隱含的vftptr來指向一個virtual function table。對於這一點,沒必要進行過多闡述了。
如果將DerivedClassA中的virtual function移動到BaseClass中,得出的結果又會是:
size of baseClass is: 4
size of direvedClassA is: 8
size of direvedClassB is: 8
size of direvedClass is: 12
此時的baseClass因為有了一個virtual function,對應的內存消耗又會增加一個virtual function table和一個vftptr,direvedClassA和direvedClassB對象除了存放一個vtbptr之外,還會存放一個繼承而來的vtfptr,大小為8也毋庸置疑。而direvedClass的大小,給我的第一感覺應該是16才對,仔細一想。腦子又糊塗了,呵呵。大概是夜太深的緣故吧。。
後記
回到“前言”處對Scott Meyers的條款討論中的困惑,無緣無故的亂將nonstatic member funtion聲明為virtualfunction,那麼每一次亂來,都將會由於virtul function table而無形中增加一些內存消耗。。 Lippman對於C++的object model講解非常細,以至於我寫下這篇博客後感覺自己一直是大而化之的一筆帶過。不知道看的人有什麼感受。。呵呵,還是想說句:writing just for remembring。
本文出自 “酋長 ” 博客,請務必保留此出處http://clement.blog.51cto.com/2235236/767167