class佔用內存問題

一、背景知識

1:首先遵從內存對齊規則

2:只有虛函數會佔4個字節,其他的函數不佔內存;無論多少個虛函數,只有這一個指針,4字節。//注意一般的函數是沒有這個指針的,而且也不佔類的內存;

3、靜態變量由於屬於所有類對象共同所有,所以不佔內存

二、典型的計算類的佔用內存的問題

1空類的問題

class CBase 

}; 
sizeof(CBase)=1;

原始的C結構經過改造,成了面向對象世界的基石——類。除了成員變量外,C++類還可以封裝成員函數和其他東西。然而除非 爲了實現虛函數和虛繼承引入的隱藏成員變量外,C++類實例的大小完全取決於一個類及其基類的成員變量!成員函數基本上不影響類實例的大小。內聯函數也不會影響類的大小。

例子:

  1. struct  B {  
  2. public :  
  3.    int  bm1;  
  4. protected :  
  5.    int  bm2;  
  6. private :  
  7.    int  bm3;  
  8.    static   int  bsm;  
  9.    void  bf();  
  10.    static   void  bsf();  
  11.    typedef   void * bpv;  
  12.    struct  N { };  

attention:1)與空的類一樣,如果單獨衡量一個空類,他所佔的內存時1;但是一旦是在類內定義另外一個空類,不佔內存,結構體也一樣。所以上面答案12;struct N{}並不影響大小

                    2)靜態變量存儲在全局變量區,不佔類的內存

2、繼承導致的類的內存分配問題

當父類中含有虛函數時,需要注意的是,每個用有虛函數的父類各自擁有一個虛指針,但是如果自己在本省再有虛函數,就不會在增加虛指針了。下面會介紹具體內存分配示意圖來解釋

例子:

 
  1. class q

  2. {

  3. virtual int change();

  4. };

  5. class r:public q

  6. {

  7. int a;

  8. };

  9. class g:public q,public r

  10. {

 
  1. virtual int ff();

  2. };

上面例子sizeof(g)=12

1)一個類無論存在幾個虛函數,只保存一個虛表指針;

2)非虛繼承時,每個父類各自擁有一個虛表指針,但是派生類無論是重載虛函數還是定義新的虛函數,都不會在增加新的虛表指針,

 如果是重載放在對應函數的位置,不是重載而是定義新的放在第一個父類虛表指向內存的後面

3.虛繼承導致的內存問題

 我們可以得到如下關於VC++虛繼承下內存佈局的結論:
1 首先排列非虛繼承的基類實例;
2 有虛基類時,爲每個基類增加一個隱藏的vbptr,除非已經從非虛繼承的類那裏繼承了一個vbptr;注意這句話的含義,下面進行解釋
3 排列派生類的新數據成員;
4 在實例最後,排列每個虛基類的一個實例。

該佈局安排使得虛基類的位置隨着派生類的不同而「浮動不定」,但是,非虛基類因此也就湊在一起,彼此的偏移量固定不變。

再有虛繼承是,一定要記住上述的規則,但是要注意第二天限制的是在此虛繼承是否還要生產虛繼承的指針原來已經產生的不會消失

例子

 1 C類會保存兩份A,因爲直接繼承A時,不是虛繼承,這種方式是不好的

2 F類A只保存一份,而且已經非虛繼承了一個虛繼承指針,不會再因爲virtual A又產生一個

三、總結

        C++程序的內存格局通常分爲四個區:全局數據區(data area),代碼區(code area),棧區(stack area),堆區(heap area)(即自由存儲區)。全局數據區存放全局變量,靜態數據和常量;所有類成員函數和非成員函數代碼存放在代碼區;爲運行函數而分配的局部變量、函數參數、返回數據、返回地址等存放在棧區;餘下的空間都被稱爲堆區。根據這個解釋,我們可以得知在類的定義時,類成員函數是被放在代碼區,而類的靜態成員變量在類定義時就已經在全局數據區分配了內存,因而它是屬於類的。對於非靜態成員變量,我們是在類的實例化過程中(構造對象)纔在棧區或者堆區爲其分配內存,是爲每個對象生成一個拷貝,所以它是屬於對象的。

        應當說明,常說的「某某對象的成員函數」,是從邏輯的角度而言的,而成員函數的存儲方式,是從物理的角度而言的,二者是不矛盾的。

        下面我們再來討論下類的靜態成員函數和非靜態成員函數的區別:靜態成員函數和非靜態成員函數都是在類的定義時放在內存的代碼區的,因而可以說它們都是屬於類的,但是類爲什麼只能直接調用靜態類成員函數,而非靜態類成員函數(即使函數沒有參數)只有類對象才能調用呢?原因是類的非靜態類成員函數其實都內含了一個指向類對象的指針型參數(即this指針),因而只有類對象才能調用(此時this指針有實值)

類中包括成員變量和成員函數。new出來的只是成員變量,成員函數始終存在,所以如果成員函數未使用任何成員變量的話,不管是不是static的,都能正常工作。需要注意的是,雖然調用不同對象的成員函數時都是執行同一段函數代碼,但是執行結果一般是不相同的。不同的對象使用的是同一個函數代碼段,它怎麼能夠分別對不同對象中的數據進行操作呢?原來C++爲此專門設立了一個名爲this的指針,用來指向不同的對象。

        需要說明,不論成員函數在類內定義還是在類外定義,成員函數的代碼段都用同一種方式存儲。不要將成員函數的這種存儲方式和inline(內聯)函數的概念混淆。不要誤以爲用inline聲明(或默認爲inline)的成員函數,其代碼段佔用對象的存儲空間,而不用inline聲明的成員函數,其代碼段不佔用對象的存儲空間。不論是否用inline聲明(或默認爲inline),成員函數的代碼段都不佔用對象的存儲空間。用inline聲明的作用是在調用該函數時,將函數的代碼段複製插人到函數調用點,而若不用inline聲明,在調用該函數時,流程轉去函數代碼段的入口地址,在執行完該函數代碼段後,流程返回函數調用點。inline與成員函數是否佔用對象的存儲空間無關,它們不屬於同一個問題,不應搞混。

 

1 疑問如下: 
(1)C++中,應該是對象纔會被分配內存空間吧??爲什麼類會有t內存大小!難道還沒實例化的時候,類就 已經有了內存空間了? 

(2)函數難道不佔用內存空間嗎?至少應該放個函數指針在裏面的吧?內存是怎樣佈局的?

(3)靜態成員應該是屬於類的,怎麼類的大小中沒有包含靜態成員的大小?

下面分別解答如下:
1)一個類定義了,它所佔的內存編譯器就已經知道了,這時只是得到它佔用的大小,並沒有分配內存操作 。也可以這樣想:編譯器肯定知道大小了,這與分配內存空間無關,知道大小了,以後實例化了才能知道要分配多大。
2)類的普通成員、靜態成員函數是不佔類內存的,至於你說的函數指針在你的類中有虛函數的時候存在一個虛函數表指針,也就是說如果你的類裏有虛函數則 sizeof(CObject)的值會增加4個字節。
其實類的成員函數 實際上與 普通的全局函數一樣。 
只不過編譯器在編譯的時候,會在成員函數上加一個參數,傳入這個對象的指針。
成員函數地址是全局已知的,對象的內存空間里根本無須保存成員函數地址。 
對成員函數(非虛函數)的調用在編譯時就確定了。 
像 myObject.Fun() 這樣的調用會被編譯成形如 _CObject_Fun( &myObject ) 的樣子。
函數是不算到sizeof中的,因爲函數是代碼,被各個對象共用,跟數據處理方式不同。對象中不必有函數指針,因爲對象沒必要知道它的各個函數的地址(調 用函數的是其他代碼而不是該對象)。 
類的屬性是指類的數據成員,他們是實例化一個對象時就爲數據成員分配內存了,而且每個對象的數據成員是對立的,而成員函數是共有的~ 
靜態成員函數與一般成員函數的唯一區別就是沒有this指針,因此不能訪問非靜態數據成員。總之,程序中的所有函數都是位於代碼區的。
3)靜態成員並不屬於某個對象,sizeof取的是對象大小。

2 計算類所佔內存的規則

A:空類的所佔的內存爲1 

B:static修飾的變量和函數不佔內存,因爲static修飾的成員保存在靜態變量區,聲明週期一直存在,爲所以類的對象共享

C:普通函數不佔內存,實質是全局函數

D:virtual修飾的函數會產生一個指向虛表的指針,但是要謹記:1)一一個類無論存在幾個虛函數,只保存一個虛表指針2)非虛繼承時,每個父類各自擁有一個虛表指針,但是派生類無論是重載虛函數還是定義新的虛函數,都不會在增加新的虛表指針3)虛繼承時,首先增加虛繼承的指針,然後當派生類是重載虛函數時,不增加新的虛表指針,當定義新的虛函數時,增加虛表指針

E static和virtual不能一起使用

   因爲靜態成員函數,可以不通過對象來調用,即沒有隱藏的this指針。virtual函數一定要通過對象來調用,即有隱藏的this指針。static function都是靜態決議的(編譯的時候就綁定了)而virtual function 是動態決議的(運行時候才綁定)

3 內存分配圖

1)繼承時,無覆蓋

2)繼承時,有覆蓋

3)多重繼承,無覆蓋

4)多重繼承,有覆蓋

相關文章
相關標籤/搜索