【C\C++】C和C++的內存管理

(一)內存分佈

    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和deletemalloc與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

https://blog.csdn.net/Z_JUAN1/article/details/81913720

https://blog.csdn.net/liu_qiqi/article/details/12489693

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