如果一個類只定義了類名,沒定義任何方法和字段,如class A{};那么class A的每個實例占用1個字節的內存,編譯器會會在這個其實例中安插一個char,以保證每個A實例在內存中有唯一的地址,如A a,b;&a!=&b。如果一個直接或是間接的繼承(不是虛繼承)了多個類,如果這個類及其父類像A一樣沒有方法沒有字段,那么這個類的每個實例的大小都是1字節,如果有虛繼承,那就不是1字節了,每虛繼承一個類,這個類的實例就會多一個指向被虛繼承父類的指針。還有一點值得說明的就是像A這樣的類,編譯器不一定會產生傳說中的那6個方法,這些方法只會在需要的時候產生,如class A沒有被任何地方使用那這些方法編譯器就沒有必要產生,如果這個類實例化了,那么會產生default constructor,而destructor則不一定產生。
如果一個類中有static data member,nonstatic data member,還有const data member,enum,那么它的內存布局會是什么樣的呢,看下面簡單的類Point:
Data member的內存布局:nonstatic data member在class object中的順序和其申明的順序一樣,static data member和const member不在class object中因為他們只有一份,被class object共享,所以static data member和const data member,枚舉并不會響應class object的大小。關于段的信息,我覺得是每個C/C++程序員必須知道的。而Point每次實例化的時候則只需要分配X,Y,maxCount需要的內存。
每個類的data member在內存中應該是連續的,如果出現數據對齊的情況,可能中間會有空白地帶。請看下面幾個類:
class BB:public AA
{
protected:
char b;
};
class CC:public BB
{
protected:
char c;
};
如果CC占用8字節內存,同樣的AA,BB都是8字節的內存,這樣的話,如果把一個指向AA實例的指針賦給一個指向CC實例的指針,那么就會把AA中的8字節直接蓋到CC的8字節上,結果CC實例中的b,c都被賦上了不是我們想要的值,這很可能會導致你的程序出問題。
父類的data member會在子類的實例中有完整的一份,這樣在有繼承關系的類之間進行類型轉換,就只用簡單的修改指針的指向。
Data Member的存取。對一個data member的存取,編譯器把對象實例的起始地址加上data member的偏移量。如CC c;
c.X=1;相當于&c+(&CC::X-1),減一其實是為了區分是指向object的指針還是指向data member的指針,指向data member的要減一。每一個data member的偏移量在編譯的時候是知道的,根據成員變量的類型和內存對齊,存在virtual繼承或是虛方法的情況編譯器會自動加上一些輔助的指針,如指向虛方法的指針,指向虛繼承父類的指針等。
在data member的存取效率上,struct member 、class member、單一繼承或是多重繼承的情況下效率都是一樣的,因為他們的存儲其實都是&obj+(&class.datamember-1)。在虛繼承的情況下,可能會影響存儲性能,如通過一個指針來存取一個指向虛繼承而來的data member,那么性能會有影響,因為在虛繼承的時候,在編譯的時候還不能確定這個data member是來自子類還是父類,只有在運行的時候才能推斷出來,其實就是多了一步指針的操作,在虛繼承中,如果是通過對象實例來操作虛繼承而來的data member,則不會有任何性能問題,因為不存在什么多態性,所有東西在編譯的時候內存地址都確定了。
虛繼承還是虛方法為了實現多態一樣,多了一步,如果不需要多態,而是通過對象實例調用相關的方法就不會有性能問題。
新聞熱點
疑難解答
圖片精選