一. 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++的指向子類對象的父類指針
先看一段代碼:
- <pre name="code" class="cpp">#include "stdafx.h"
- class A
- {
- public:
- A(){a = 2;};
- void fun0()
- {
- printf("A::fun0 a = %d \n", a);
- }
- virtual int fun1()
- {
- printf("A::fun1 a = %d \n" , a);
- return a;
- }
- int a;
- };
- class B : public A
- {
- public:
- B(){
- a = 4;
- b = 111;
- };
- void fun0()
- {
- printf("B::fun0 a = %d \n" , a);
- }
- virtual int fun1()
- {
- printf("B::fun1 a = %d \n" , a);
- return a;
- }
- int a;
- int b;
- };
- class C
- {
- };
- class D
- {
- public:
- int M;
- virtual int getM()
- {
- return M;
- }
- };
- int main(int argc, char* argv[])
- {
- printf("sizeof(A) = %d \n" , sizeof(A));
- printf("sizeof(B) = %d \n" , sizeof(B));
- printf("sizeof(C) = %d \n" , sizeof(C));
- printf("---------------\n");
- A *pa = new B();
- pa->fun0();
- pa->fun1();
- printf("pa地址 = %X \n" , pa);
- printf("pa->a = %d \n" , pa->a);
- //printf("pa->b = %d \n" , pa->b);
- printf("---------------\n");
- B *pb = dynamic_cast<B *>(pa);
- pb->fun0();
- pb->fun1();
- printf("pb->a = %d \n" , pb->a);
- printf("---------------\n");
- D *d = (D*)pb;
- printf("D地址 = %X \n" , d);
- printf("d->getM() = %d \n\n" ,d->getM());
- printf("(d->M) = %d \n" ,d->M);
- printf("d->M 地址 %X \n\n" ,&(d->M));
- printf("pa->a = %d \n" , pa->a);
- printf("pa->a 地址 %X \n" , &(pa->a));
- delete pb;
- //delete pa;
- return 0;
- }</pre><br>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <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 |
這時,
- pa->fun0();
然後我的理解是this指針作爲參數傳遞給fun0, 其實傳遞的是內存數據段的首地址(也就是上面虛函數指針的地址) .
輸出的a = 2時, 其實A::fun0()在編譯的時候, 已經確定a的值, 是首地址+1的值. 也就是 A::a 父類的成員變量的 a = 2了.
- pa->fun1();
同樣, B::fun1() , 在編譯完的時候 就已經 確定裏面涉及的 a值 是 首地址+2了. 也就是 B::a 父類的成員變量 a = 4.
- pa->a;
- //printf("pa->b = %d \n" , pa->b);
首先sizeof(A) = 8. 雖然傳來的首地址可用內存是sizeof(B) =16 .
但是編譯器認爲A的對象就是8. 所以下面的內存
B::a 父類的成員變量 a = 4 |
B::b 父類的成員變量 b = 111 |
對於編譯器來說是越界的. 所以也找不到B裏面b...
或者如果沒有子類的干擾的話 , 直接就能看出int b 就不屬於類A.
- B *pb = dynamic_cast<B *>(pa);
然後 父類的指針指向 轉換成子類指針. 當然強制轉換也可以的. B *pb = (B *)(pa);但是這樣是不安全的. 類B 中的最大偏移爲3. 如果pa是指向的是父類的對象, 最大偏移量是1 .
在運行時將會出現讀取內存越界的情況. static_cast 也是隻取到pa的地址, 結果也是相同的情況 ,
- A * a = new A();
- B *pb = (B *)(a);
- pb->fun0();
- pb->fun1();
- printf("pb->a = %d \n" , pb->a);
dynamic_cast 是動態轉換, 在運行時類的類型將會與子類匹配, 如果pa 是指向子類的指針, 則返回pa, 否則返回NULL.
下面的代碼, 意思和上面差不多, 就不一一解釋了.關於D類型的代碼, 下面還會繼續解釋.
總結:
1. 我的理解, this就是指向對象數據段的首地址
2. 類的成員函數編譯時把成員變量編譯成對this指針的偏移量.
3. 虛函數表的指針地址就是this的值.
4. 驗證.還是上面的代碼, 類D和A的類結構相同.
- d->getM();
pd的M值和pa的a值相同, 地址相同.
這個就這麼多了...