(一)內存分佈
1.1 C
- BSS段: 用來存放程序中未初始化的全局變量和未初始化的靜態變量。
- 數據段:用來存放程序中已初始化的全局變量和已經初始化的靜態變量。
- 代碼段:用來存放程序執行代碼。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等
- 堆:堆是用於存放進程運行中被動態分配的內存段,它的大小並不固定,可動態擴張或縮減。當進程調用malloc/free等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張)/釋放的內存從堆中被剔除(堆被縮減)
- 棧:棧又稱堆棧, 存放程序的局部變量(但不包括static聲明的變量,static意味着在數據段中存放變量)。除此以外在函數被調用時,棧用來傳遞參數和返回值。由於棧的先進先出特點,所以棧特別方便用來保存/恢復調用現場。
1.2 C++
- 棧:存放爲運行函數而分配的局部變量、函數參數、返回數據、返回地址等;在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
- 堆:就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那麼在程序結束後,操作系統會自動回收。
- 自由存儲區:就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
- 全局/靜態存儲區:存放全局變量、靜態數據,const常量。在以前的C語言中,全局變量又分爲初始化的和未初始化的,在C++裏面沒有這個區分了,他們共同佔用同一塊內存區。
- 常量存儲區:這是一塊比較特殊的存儲區,他們裏面存放的是不允許被修改的常量字符串。
(二)內存分配方式
2.1 C中內存的動態分配
2.1.1 malloc、relloc、calloc和free
1、void* malloc(字節數):無初始化的申請一塊內存
2、void* calloc(單個元素字節,元素個數):初始化爲0的申請一塊內存
3、void* realloc(地址p,字節數):將p指針對應的內存擴容,藉助別人的圖更深的理解一下realloc這個函數:
4、 free(地址p):將p指向的地址歸還給堆
1.1.2 C中內存分配時常見的內存泄漏
1、內存忘記釋放
int main()
{
int *pTest = (int *)malloc(10*sizeof( int)); //申請內存
assert(NULL != pTest);
......... //在這塊空間上做了一些事情
return 0; //沒有釋放之前申請的空間直接退出函數
}
2、程序邏輯不清,以爲釋放了,實則沒釋放
int main()
{
int *p1 = (int *)malloc(10*sizeof(int));
int *p2 = (int *)malloc(10*sizeof(int));
............; //在這個兩塊空間上做一些操作
p1 = p2; //讓p1和p2同時指向p1對應的空間,這個時候沒有事先保存p2的空間地址,p2空間地址丟失l
free(p1); //釋放掉p1的空間
free(p2); //釋放掉p2的空間,但實際上p1和p2已經指向了同一塊地址,這句話造成了空間的重複釋放,但p2申請的空間並未釋放
return 0; //退出函數造成p2內存泄漏
}
3、將堆破壞
int main()
{
char *p3 = (char *)malloc(5);
strcpy(p, "hello world!");//字串拷貝時會造成越界,將堆破壞掉
free(pTest3);
return 0;
}
4、釋放時傳入的空間和釋放時穿入的空間地方不相同。
int main()
{
int *p4 = (int *)malloc(10*sizeof( int));
assert(NULL != pTest4);
p4[0] = 0;
p4++; //p4的指向地址已經變更!!!
.........; //做一些操作
free(p4); //釋放掉已經變更的地址
return 0;
}
1.1.3 拓展
1、malloc實際被分配的空間:實際上這塊空間會大於程序員申請的size,第一個原因是因爲不同機器受到的類型限制進字節對齊。第二個原因是申請的空間需要存放關於這塊空間信息的"頭部"。因此會多程序員實際申請的空間。
因爲malloc函數控制的塊是用鏈表聯繫在一起的,並不是一個連續的空間,所以程序員實際請求的空間大小會被被記錄在頭部的size字段中。malloc最後只會返回空閒塊的首地址,而不是實際分配的首地址如下圖所示:
下面是《C程序設計語言》中給出的通過union進行的頭部實現,其中假定機器的受限類型爲long。
typedef long Align;/*按照long類型的邊界對齊*/
union header/*塊的頭部*/
{
struct
{
union header *ptr;/*空閒塊鏈表中的下一塊*/
unsigned size;/*本塊的大小*/
}s;
Align x;/*強制塊對齊*/
};
2、malloc的將空間分配過程:malloc可用的內存塊連接爲一個長長的列表 -- 空閒鏈表。程序員調用malloc函數申請空間時,malloc會掃描空閒鏈表,直到找到一個足夠大的塊爲止(首次適應)因此每次調用malloc時並不是花費了完全相同的時間,如果找到了一塊恰好與請求的大小相符的空間,則將其從鏈表中移走並返回給用戶。如果該塊太大,則將其分爲兩部分,尾部的部分分給用戶,剩下的部分留在空閒鏈表中並更改頭部信息。接下來,將分配給用戶的那塊內存傳給用戶,並將剩下的那塊(如果有的話)返回到連接表上。調用free函數時,它將用戶釋放的內存塊連接到空閒鏈上。到最後,空閒鏈會被切成很多的小內存片段,如果這時用戶申請一個大的內存片段,那麼空閒鏈上可能沒有可以滿足用戶要求的片段了。於是,malloc函數請求延時,並開始在空閒鏈上翻箱倒櫃地檢查各內存片段,對它們進行整理,將相鄰的小空閒塊合併成較大的內存塊。
2.2 C++中動態內存分配
2.2.1 new、new [ type ] 、delete 和 delete []
1、new
第一步:調用 void* operator new(szie_t size)函數申請空間,成功 ->返回地址, 失敗 -> 內部完成對空間不足做出的應對措施(提供了一個函數,用戶自己來提供,檢測是否存在已經申請的但不使用的空,沒有就出錯,拋一個異常)
第二步:調用構造函數初始化對象。
2、new [ type ]
第一步:調用void* operator new[ ]申請空間
第二步:調用N次構造函數。 其實他在申請空間的時候會在多申請4個字節的空間,來記錄對象的個數。(若沒有析構函數,就不用記錄,這裏記錄對象的個數,其實是爲了在析構的時候知道應該析構多少次)
3、delete
第一步:調用析構函數:清理對象中的資源
第二步:調用void* operator delete(size_t size)釋放空間
4、delete [ ]
第一步:清理對象中的資源-->去空間前4個字節中取字節個數N,調用N次析構,遵循先構造的後析構規則
第二步:調用operator delete[ ] (從空間的起始位置釋放)->operator delete-->free
2.2.2 malloc和free、new和delete、new [ ] 和 delete [ ]搭配不當造成的問題
1、無析構
malloc --> delete/delete [ ] -> 底層都是用free實現的
new --> delete/delete [ ] --> 底層都是用free實現的
new [ ] --> delete/delete [ ] -> 底層都是用free實現的
注:列舉了在沒有提供析構函數的情況下,分別使用malloc、new、new[ ]來申請空間,用delete、delete[ ]和free釋放空間產生的問題
2、有析構
new --> free:對象資源沒有清理
--> delete[ ]:崩潰(多釋放4字節)
new [ ] --> free:對象資源無清理&不會從起始位置進行釋放,崩潰
--> delete:只會銷燬一個對象,(N-1)個對象沒有清理&不會從起始位置釋放
注:列舉在程序員提供了析構函數的情況下,分別使用new、new[ ]申請空間,用delete、delete[ ]和free釋放空間產生的問題;
2.2.3 拓展
1、讓內存高效使用:考慮一個問題 - > 假如我們要申請1000塊空間,每次都會調用malloc,前面說過,每調malloc一次內部會多出36個字節,每次都會多出36000個字節,並且系統提供堆的分配算法效率低,每一次malloc的空間是不連續的,會造成內存碎片,這樣就會產生很大的浪費。
因此我們一次性malloc一大塊,每次拿的時候直接從malloc出來的那塊空間裏面拿就可以了。但是有一個問題,malloc出來的不會進行構造,因爲我們需要採用new(地址) 類型,對其進行類的構造。此時它內部調用 operator new(size_t ,void* where ){return where;} 不需要指定大小,只需給位置就行。
2、爲什麼有了malloc和free還要設計new和delete:malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。對於非內部數據類型的對象而言,光用maloc/free無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。由於malloc/free是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加於malloc/free。 因此C++語言需要一個能完成動態內存分配和初始化工作的運算符new,以及一個能完成清理與釋放內存工作的運算符delete。
3、爲什麼設計了new和delete還要讓malloc和free存在:因爲語言要向上兼容,並且C++程序經常要調用C函數,而C程序只能用malloc/free管理動態內存。
參考文章:
https://blog.csdn.net/qq_28584889/article/details/88758197
https://blog.csdn.net/zxx910509/article/details/62881131