史上最全的C++面試寶典(六)—— 動態記憶體

2020-08-09 16:35:30

參考:https://www.runoob.com/cplusplus/cpp-tutorial.html

本教學旨在提取最精煉、實用的C++面試知識點,供讀者快速學習及本人查閱複習所用。

目錄

第六章  動態記憶體

6.1  new和delete運算子

6.2  動態記憶體分配

6.3  相關面試題


第六章  動態記憶體

C++ 程式中的記憶體分爲兩個部分:

  • 棧:在函數內部宣告的所有變數都將佔用棧記憶體。
  • 堆:這是程式中未使用的記憶體,在程式執行時可用於動態分配記憶體。

6.1  new和delete運算子

在 C++ 中,可以使用特殊的運算子爲給定型別的變數在執行時分配堆內的記憶體,這會返回所分配的空間地址。這種運算子即 new 運算子。如果不再需要動態分配的記憶體空間,可以使用 delete 運算子,刪除之前由 new 運算子分配的記憶體。

#include <iostream>
using namespace std;
 
int main ()
{
   double* pvalue  = NULL; // 初始化爲 null 的指針
   pvalue  = new double;   // 爲變數請求記憶體
 
   *pvalue = 29494.99;     // 在分配的地址儲存值
   cout << "Value of pvalue : " << *pvalue << endl;
 
   delete pvalue;         // 釋放記憶體
 
   return 0;
}

6.2  動態記憶體分配

6.2.1  陣列的動態記憶體分配

假設我們要爲一個字元陣列(一個有 20 個字元的字串)分配記憶體,我們可以使用上面範例中的語法來爲陣列動態地分配記憶體:

char* pvalue  = NULL;   // 初始化爲 null 的指針
pvalue  = new char[20]; // 爲變數請求記憶體

要刪除我們剛纔建立的陣列,語句如下:

delete [] pvalue;        // 刪除 pvalue 所指向的陣列

二維陣列範例:

#include <iostream>
using namespace std;
 
int main()
{
    int **p;   
    int i,j;   //p[4][8] 
    //開始分配4行8列的二維數據   
    p = new int *[4];
    for(i=0;i<4;i++){
        p[i]=new int [8];
    }
 
    for(i=0; i<4; i++){
        for(j=0; j<8; j++){
            p[i][j] = j*i;
        }
    }   
    //列印數據   
    for(i=0; i<4; i++){
        for(j=0; j<8; j++)     
        {   
            if(j==0) cout<<endl;   
            cout<<p[i][j]<<"\t";   
        }
    }   
    //開始釋放申請的堆   
    for(i=0; i<4; i++){
        delete [] p[i];   
    }
    delete [] p;   
    return 0;
}

6.2.2  物件的動態記憶體分配

物件與簡單的數據型別沒有什麼不同:

#include <iostream>
using namespace std;
 
class Box
{
   public:
      Box() { 
         cout << "呼叫建構函式!" <<endl; 
      }
      ~Box() { 
         cout << "呼叫解構函式!" <<endl; 
      }
};
 
int main( )
{
   Box* myBoxArray = new Box[4];
 
   delete [] myBoxArray; // 刪除陣列
   return 0;
}

如果要爲一個包含四個 Box 物件的陣列分配記憶體,建構函式將被呼叫 4 次,同樣地,當刪除這些物件時,解構函式也將被呼叫相同的次數。

6.3  相關面試題

Q:new/delete具體步驟

A:使用new操作符來分配物件記憶體時會經歷三個步驟:

  • 第一步:呼叫operator new 函數分配一塊足夠大的,原始的,未命名的記憶體空間以便儲存特定型別的物件。
  • 第二步:編譯器執行相應的建構函式以構造物件,併爲其傳入初值。
  • 第三部:物件構造完成後,返回一個指向該物件的指針。

使用delete操作符來釋放物件記憶體時會經歷兩個步驟:

  • 第一步:呼叫物件的解構函式。
  • 第二步:編譯器呼叫operator delete函數釋放記憶體空間。

Q:new/delete與malloc/free的區別是什麼?

A:

  • malloc/free是C語言的標準庫函數, new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體;
  • malloc/free不會去自動呼叫構造和解構函式,對於基本數據型別的物件而言,光用malloc/free無法滿足動態物件的要求;
  • malloc/free需要指定分配記憶體的大小,而new/delete會自動計算所需記憶體大小;
  • new返回的是指定物件的指針,而malloc返回的是void*,因此malloc的返回值一般都需要進行強制型別轉換。

Q:C++記憶體管理

A:在C++中,虛擬記憶體分爲程式碼段、數據段、BSS段、堆區、棧區以及檔案對映區六部分。

  • 程式碼段:包括只讀儲存區和文字區,其中只讀儲存區儲存字串常數,文字區儲存程序的機器程式碼。
  • 數據段:儲存程序中已初始化的全域性變數和靜態變數
  • BSS段:儲存未初始化的全域性變數和靜態變數(區域性+全域性),以及所有被初始化爲0的全域性變數和靜態變數。
  • 堆區:呼叫new/malloc函數時在堆區動態分配記憶體,同時需要呼叫delete/free來手動釋放申請的記憶體。
  • 對映區:儲存動態鏈接庫以及呼叫mmap函數進行的檔案對映
  • 棧區:使用棧空間儲存函數的返回地址、參數、區域性變數、返回值

Q:記憶體的分配方式

A:記憶體分配方式有三種:

  1. 靜態儲存區,是在程式編譯時就已經分配好的,在整個執行期間都存在,如全域性變數、常數。
  2. 棧上分配,函數內的區域性變數就是從這分配的,但分配的記憶體容易有限。
  3. 堆上分配,也稱動態分配,如我們用new,malloc分配記憶體,用delete,free來釋放的記憶體。

Q:簡單介紹記憶體池?

A:記憶體池是一種記憶體分配方式。通常我們習慣直接使用new、malloc申請記憶體,這樣做的缺點在於所申請記憶體塊的大小不定,當頻繁使用時會造成大量的記憶體碎片並進而降低效能。記憶體池則是在真正使用記憶體之前,預先申請分配一定數量、大小相等(一般情況下)的記憶體塊留作備用。當有新的記憶體需求時,就從記憶體池中分出一部分記憶體塊,若記憶體塊不夠再繼續申請新的記憶體。這樣做的一個顯著優點是,使得記憶體分配效率得到提升。

Q:簡單描述記憶體漏失?

A:記憶體漏失一般是指堆記憶體的泄漏,也就是程式在執行過程中動態申請的記憶體空間不再使用後沒有及時釋放,導致那塊記憶體不能被再次使用。

Q:C++中的不安全是什麼概念?

A:C++中的不安全包括兩種:一是程式得不到正確的結果,二是發生不可預知的錯誤(佔用了不該用的記憶體空間)。可能會發生如下問題:

  1. 最嚴重的:記憶體漏失,程式崩潰;
  2. 一般嚴重的:發生一些邏輯錯誤,且不便於偵錯;
  3. 較輕的:丟失部分數據,就像強制轉換一樣。

Q:記憶體中的堆與棧有什麼區別?

A:

  1. 申請方式:棧由系統自動分配和管理,堆由程式設計師手動分配和管理。
  2. 效率:棧由系統分配,計算機底層對棧提供了一系列支援:分配專門的暫存器儲存棧的地址,壓棧和入棧有專門的指令執行,因此,其速度快,不會有記憶體碎片;堆由程式設計師分配,堆是由C/C++函數庫提供的,機制 機製複雜,需要一些列分配記憶體、合併記憶體和釋放記憶體的演算法,因此效率較低,可能由於操作不當產生記憶體碎片。
  3. 擴充套件方向:棧從高地址向低地址進行擴充套件,堆由低地址向高地址進行擴充套件。
  4. 程式區域性變數是使用的棧空間,new/malloc動態申請的記憶體是堆空間;同時,函數呼叫時會進行形參和返回值的壓棧出棧,也是用的棧空間。