c++的內存分配(轉載)

一.  c++的內存分配:

http://blog.sina.com.cn/s/blog_3cba7ec10100hh6p.html

1、高位地址:棧(存放着局部變量和函數參數等數據),向下生長   (可讀可寫可執行) 
2、                   堆(給動態分配內存時使用),向上生長             (可讀可寫可執行) 
3、                   數據段(保存全局數據和靜態數據)                    (可讀可寫不可執行) 
4、低位地址:代碼段(保存代碼)                                (可讀可執行不可寫) 

代碼段就是存儲程序文本的,所以有時候也叫做文本段,指令指針中的指令就是從這裏取得。這個段一般是可以被共享的,比如你在Linux開了2個Vi來編輯文本,那麼一般來說這兩個Vi是共享一個代碼段的,但是數據段不同(這點有點類似C++中類的不同對象共享相同成員函數)。 

數據段是存儲數據用的,還可以分成初始化爲非零的數據區,BSS,和堆(Heap)三個區域。初始化非零數據區域一般存放靜態非零數據和全局的非零數據。BSS是Block Started by Symbol的縮寫,原本是彙編語言中的術語。該區域主要存放未初始化的全局數據和靜態數據。還有就是堆了,這個區域是給動態分配內存是使用的,也就是用malloc等函數分配的內存就是在這個區域裏的。它的地址是向上增長的。C裏面區分初始化和非初始化, C++裏面不區分了.  
常量存儲區  這是一塊比較特殊的存儲區,他們裏面存放的是常量,不允許修改.


最後一個堆棧段(注意,堆棧是Stack,堆是Heap,不是同一個東西),堆棧可太重要了,這裏存放着局部變量和函數參數等數據。例如遞歸算法就是靠棧實現的。棧的地址是向下增長的。有些資料還有
這麼一說(自由存儲區) 就是那些由 malloc 等分配的內存塊,他和堆是十分相似的,不過它是用 free 來結束自己的生命的.

========高地址   ======= 
程序棧 ,堆棧段 (向下增長) 內存地址減小的方向增長
============== 
堆 (向上增長) 向着內存地址增加的方向

============== 
BSS 
------ 
非零數據
 
=========低地址   ======= 
=========       ======= 
代碼           代碼段 
=========       ======= 

 需要注意的是,代碼段和數據段之間有明確的分隔,但是數據段和堆棧段之間沒有,而且棧是向下增長,堆是向上增長的,因此理論上來說堆和棧會“增長到一起”,但是操作系統會防止這樣的錯誤發生,所以不用過分擔心。

二.  c++的 this 

對於類,編譯器會自動爲其生成五個隱式成員函數。分別爲

1.  默認構造函數

2.  默認析構函數

3.  賦值操作符

4.  複製構造函數

5.   this指針

對於this 指針 http://lwzy-crack.blog.163.com/blog/static/95272042200962523450519/

http://www.cnblogs.com/st_zhang/archive/2010/09/07/1820488.html

this指針並不是對象本身的一部分,不會影響sizeof(“對象”)的結果。this作用域是在類內部,當在類的非靜態成員函數中訪問類的非靜態成員的時候,編譯器會自動將對象本身的地址作爲一個隱含參數傳遞給函數。也就是說,即使你沒有寫上this指針,編譯器在編譯的時候也是加上this的,它作爲非靜態成員函數的隱含形參,對各成員的訪問均通過this進行。

其實this 只是作爲類的非靜態成員函數的參數.並不是類內部的隱藏的成員變量.

靜態成員函數沒有this指針.

類的對象都有數據段 和 代碼段. 但是代碼段是公用的...通過非靜態成員函數的參數 this , 就能確保每個對象調用的自己的成員變量.

(空類的sizeof = 1. 有虛函數的話 sizeof再加上虛函數表的指針的大小4.)

三.  c++的重載技術 

先說extern “C”.

在C++中,函數void foo( int x, int y )與void foo( int x, float y)編譯生成的符號是不相同的,後者爲_foo_int_float。

void foo( int x, int y );

  該函數被C編譯器編譯後在符號庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱爲“mangledname”)。_foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y)編譯生成的符號是不相同的,後者爲_foo_int_float。

四.  c++的父類 子類 內存分配

下面就單繼承分爲幾種情況闡述:
1.普通繼承+父類無virtual函數
    若子類沒有新定義virtual函數  此時子類的佈局是 : 由低地址->高地址  爲父類的元素(沒有vptr),子類的元 素(沒有vptr).
   若子類有新定義virtual函數  此時子類的佈局是 : 由低地址->高地址  爲父類的 元素(沒有vptr),子類的元 素(包含vptr,指向vtable.)


2. 普通繼承+父類有virtual函數
    不管子類沒有新定義virtual函數  此時子類的佈局是 : 由低地址->高地址  爲父類的 元素(包含vptr), 子類的元 素.
    如果子類 有新定義的virtual函數,那麼在父 類的vptr(也就是第 一個vptr)對應的vtable中添加一 個函數指針.

vptr    (子類和父類的虛函數表指針)
父類的成員變量
子類的成員變量

五.  c++的指向子類對象的父類指針

先看一段代碼: 

  1. <pre name="code" class="cpp">#include "stdafx.h"  
  2. class A   
  3. {  
  4. public:  
  5.     A(){a = 2;};  
  6.   
  7.   
  8.     void fun0()  
  9.     {  
  10.         printf("A::fun0 a = %d \n", a);  
  11.     }  
  12.   
  13.   
  14.     virtual int fun1()  
  15.     {  
  16.         printf("A::fun1 a = %d \n" , a);  
  17.         return a;  
  18.     }  
  19.   
  20.   
  21.     int a;  
  22.       
  23. };  
  24. class B : public A  
  25. {  
  26. public:  
  27.     B(){  
  28.         a = 4;  
  29.         b = 111;  
  30.     };  
  31.   
  32.   
  33.     void fun0()  
  34.     {  
  35.         printf("B::fun0 a = %d \n" , a);  
  36.     }  
  37.   
  38.   
  39.     virtual int fun1()  
  40.     {  
  41.         printf("B::fun1 a = %d \n" , a);  
  42.         return a;  
  43.     }  
  44.   
  45.   
  46.     int a;  
  47.     int b;  
  48. };  
  49. class C   
  50. {  
  51.   
  52.   
  53. };  
  54. class D   
  55. {  
  56. public:  
  57.     int M;  
  58.     virtual int getM()  
  59.     {  
  60.       return M;  
  61.     }  
  62. };  
  63. int main(int argc, char* argv[])  
  64. {  
  65.     printf("sizeof(A) = %d \n" , sizeof(A));  
  66.     printf("sizeof(B) = %d \n" , sizeof(B));  
  67.     printf("sizeof(C) = %d \n" , sizeof(C));  
  68.     printf("---------------\n");  
  69.   
  70.     A *pa = new B();  
  71.     pa->fun0();  
  72.     pa->fun1();  
  73.     printf("pa地址 = %X \n" , pa);  
  74.     printf("pa->a = %d \n" , pa->a);  
  75.     //printf("pa->b = %d \n" , pa->b);  
  76.   
  77.     printf("---------------\n");  
  78.     B *pb =  dynamic_cast<B *>(pa);  
  79.     pb->fun0();  
  80.     pb->fun1();  
  81.   
  82.     printf("pb->a = %d \n" , pb->a);  
  83.   
  84.     printf("---------------\n");  
  85.     D *d = (D*)pb;  
  86.     printf("D地址 = %X \n" , d);  
  87.     printf("d->getM() = %d \n\n" ,d->getM());  
  88.   
  89.     printf("(d->M) = %d \n" ,d->M);  
  90.     printf("d->M 地址 %X \n\n" ,&(d->M));  
  91.   
  92.     printf("pa->a = %d \n" , pa->a);  
  93.     printf("pa->a 地址 %X \n" , &(pa->a));  
  94.       
  95.     delete pb;  
  96.     //delete pa;  
  97.     return 0;  
  98. }</pre><br>  
  99. <pre></pre>  
  100. <pre></pre>  
  101. <pre></pre>  
  102. <pre></pre>  
  103. <pre></pre>  
  104. <pre></pre>  
  105. <pre></pre>  
  106. <pre></pre>  
  107. <pre></pre>  
  108. <pre></pre>  
  109. <pre></pre>  
  110. <pre></pre>  
  111. <pre></pre>  

運行結果



 代碼解讀.在main中....

 1.  sizeof(A) = 8. A類中 有虛函數表指針vptr 再加上 int a的值 = sizeof(int). 4 + 4 =8;

 2.  sizeof(B) =16 B類的大小 是  sizeof(A) + 2 *sizeof(int) = 16.
 3.  sizeof(C) = 1 空類的大小爲1.

 4. A *pa = new B(); 父類的指針指向了子類的對象. 

       這時pa用的代碼段是A::fun0(), A::fun1(); 

pa是指向子類對象的首地址, 用的數據段是子類對象內存數據.如下.

vptr (虛函數指針)指向B::fun1()
A::a    父類的成員變量 a = 2
B::a        父類的成員變量 a = 4
B::b        父類的成員變量 b = 111

這時, 

  1. pa->fun0();  
調用的是pa的代碼段 A::fun0();

       然後我的理解是this指針作爲參數傳遞給fun0, 其實傳遞的是內存數據段的首地址(也就是上面虛函數指針的地址) .

       輸出的a = 2時,  其實A::fun0()在編譯的時候, 已經確定a的值, 是首地址+1的值. 也就是  A::a    父類的成員變量的 a = 2了.

  1. pa->fun1();  
是虛函數, 運行時動態綁定 通過數據段中的vptr獲取虛函數 B::fun1() .

同樣, B::fun1() ,  在編譯完的時候 就已經 確定裏面涉及的 a值 是 首地址+2了. 也就是  B::a        父類的成員變量 a = 4.

  1. pa->a;  
其實在編譯的時候,  編譯器並不知道pa指向的是子類對象.  編譯器編譯後變成首地址+1的值.

  1. //printf("pa->b = %d \n" , pa->b);  
屏掉的這句,  去掉註釋將會出現編譯錯誤.  error C2039: 'b' : is not a member of 'A'. 

首先sizeof(A) = 8. 雖然傳來的首地址可用內存是sizeof(B) =16 .

但是編譯器認爲A的對象就是8. 所以下面的內存

B::a        父類的成員變量 a = 4
B::b        父類的成員變量 b = 111

對於編譯器來說是越界的. 所以也找不到B裏面b...

或者如果沒有子類的干擾的話 , 直接就能看出int b 就不屬於類A.

  1. B *pb =  dynamic_cast<B *>(pa);  
首先 用到dynamic_cast時, 要在工程的設置裏面. 找到C+ ->C++語言->允許RTTI.

然後 父類的指針指向 轉換成子類指針. 當然強制轉換也可以的. B *pb =  (B *)(pa);但是這樣是不安全的. 類B 中的最大偏移爲3. 如果pa是指向的是父類的對象, 最大偏移量是1 .

在運行時將會出現讀取內存越界的情況. static_cast  也是隻取到pa的地址, 結果也是相同的情況 , 

  1. A * a = new A();  
  2. B *pb = (B *)(a);  
  3. pb->fun0();  
  4. pb->fun1();  
  5. printf("pb->a = %d \n" , pb->a);  




dynamic_cast 是動態轉換, 在運行時類的類型將會與子類匹配, 如果pa 是指向子類的指針, 則返回pa, 否則返回NULL.

下面的代碼, 意思和上面差不多, 就不一一解釋了.關於D類型的代碼, 下面還會繼續解釋.

總結:

1. 我的理解, this就是指向對象數據段的首地址

2. 類的成員函數編譯時把成員變量編譯成對this指針的偏移量.

3. 虛函數表的指針地址就是this的值.

4. 驗證.還是上面的代碼, 類D和A的類結構相同. 

  1. d->getM();  
取的還是B::fun1(). 調用了pb的虛函數表.

pd的M值和pa的a值相同, 地址相同.

這個就這麼多了...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章