動態記憶體

在前面章節中看到的程式裡,所有記憶體需求都是在程式執行前透過定義所需變數來確定的。但在某些情況下,程式的記憶體需求只能在執行時確定。例如,當所需記憶體取決於使用者輸入時。在這些情況下,程式需要動態分配記憶體,為此,C++ 語言集成了 newdelete 運算子。

運算子 new 和 new[]

動態記憶體使用 new 運算子進行分配。new 後面跟著一個數據型別說明符,如果需要一個包含多個元素的序列,還需要在方括號 [] 中指定元素的數量。它返回一個指向新分配記憶體塊起始位置的指標。其語法是:

pointer = new type
pointer = new type [number_of_elements]

第一個表示式用於分配記憶體以容納一個 type 型別的單個元素。第二個表示式用於分配一個 type 型別的元素塊(一個數組),其中 number_of_elements 是一個整數值,表示元素的數量。例如:

1
2
int * foo;
foo = new int [5];

在這種情況下,系統動態分配了可以容納五個 int 型別元素的空間,並返回一個指向該序列第一個元素的指標,該指標被賦給 foo(一個指標)。因此,foo 現在指向一個有效的記憶體塊,該記憶體塊有足夠的空間容納五個 int 型別的元素。



在這裡,foo 是一個指標,因此,foo 指向的第一個元素可以透過表示式 foo[0] 或表示式 *foo(兩者等價)來訪問。第二個元素可以透過 foo[1]*(foo+1) 來訪問,依此類推……

宣告一個普通陣列和使用 new 為記憶體塊動態分配記憶體之間存在著實質性的區別。最重要的區別是,常規陣列的大小必須是一個常量表達式,因此其大小必須在程式設計時、執行之前就確定。而由 new 執行的動態記憶體分配允許在執行時使用任何變數值作為大小來分配記憶體。

我們的程式所請求的動態記憶體是由系統從記憶體堆中分配的。然而,計算機記憶體是有限的資源,它可能會被耗盡。因此,不能保證所有使用 new 運算子分配記憶體的請求都會被系統批准。

C++ 提供了兩種標準機制來檢查分配是否成功:

一種是透過處理異常。使用此方法,當分配失敗時,會丟擲一個型別為 bad_alloc 的異常。異常是 C++ 的一個強大功能,將在後面的教程中進行解釋。但現在,您應該知道,如果這個異常被丟擲並且沒有被特定的處理程式捕獲,程式執行將終止。

這種異常方法是 new 預設使用的方法,並且在像下面這樣的宣告中使用:

1
foo = new int [5];  // if allocation fails, an exception is thrown  

另一種方法被稱為 nothrow,當使用它時,如果記憶體分配失敗,它不會丟擲 bad_alloc 異常或終止程式,而是使 new 返回的指標成為一個空指標,程式會繼續正常執行。

這種方法可以透過使用一個名為 nothrow 的特殊物件來指定,該物件在標頭檔案 <new> 中宣告,作為 new 的引數:

1
foo = new (nothrow) int [5];

在這種情況下,如果這塊記憶體分配失敗,可以透過檢查 foo 是否為空指標來檢測到失敗:

1
2
3
4
5
int * foo;
foo = new (nothrow) int [5];
if (foo == nullptr) {
  // error assigning memory. Take measures.
}

與異常相比,nothrow 方法可能會產生效率較低的程式碼,因為它意味著在每次分配後都要顯式檢查返回的指標值。因此,通常首選異常機制,至少對於關鍵的記憶體分配是這樣。儘管如此,由於其簡單性,接下來的大多數例子將使用 nothrow 機制。

運算子 delete 和 delete[]

在大多數情況下,動態分配的記憶體僅在程式內的特定時間段內需要;一旦不再需要,就可以將其釋放,以便該記憶體可再次用於其他動態記憶體請求。這就是 delete 運算子的目的,其語法是:

1
2
delete pointer;
delete[] pointer;

第一條語句釋放使用 new 分配的單個元素的記憶體,第二條語句釋放使用 new 和方括號 ([]) 中的大小為元素陣列分配的記憶體。

傳遞給 delete 的引數值應該是一個指向先前用 new 分配的記憶體塊的指標,或者是一個空指標(在是空指標的情況下,delete 不產生任何效果)。

// rememb-o-matic
#include <iostream>
#include <new>
using namespace std;

int main ()
{
  int i,n;
  int * p;
  cout << "How many numbers would you like to type? ";
  cin >> i;
  p= new (nothrow) int[i];
  if (p == nullptr)
    cout << "Error: memory could not be allocated";
  else
  {
    for (n=0; n<i; n++)
    {
      cout << "Enter number: ";
      cin >> p[n];
    }
    cout << "You have entered: ";
    for (n=0; n<i; n++)
      cout << p[n] << ", ";
    delete[] p;
  }
  return 0;
}
How many numbers would you like to type? 5
Enter number : 75
Enter number : 436
Enter number : 1067
Enter number : 8
Enter number : 32
You have entered: 75, 436, 1067, 8, 32,

請注意,new 語句中方括號內的值是使用者輸入的變數值 (i),而不是一個常量表達式:

1
p= new (nothrow) int[i];

使用者總是有可能為 i 輸入一個非常大的值,以至於系統無法為其分配足夠的記憶體。例如,當我試圖為“How many numbers”問題輸入 10 億這個值時,我的系統無法為程式分配那麼多記憶體,然後我收到了我們為此情況準備的文字訊息 (Error: memory could not be allocated)。

一個良好的程式設計實踐是,程式應始終能夠處理記憶體分配失敗的情況,無論是透過檢查指標值(如果使用 nothrow)還是透過捕獲相應的異常。

C 語言中的動態記憶體

C++ 集成了 newdelete 運算子來分配動態記憶體。但在 C 語言中並沒有這些運算子;取而代之的是,它使用了一個庫解決方案,即在標頭檔案 <cstdlib>(在 C 中稱為 <stdlib.h>)中定義的函式 malloccallocreallocfree。這些函式在 C++ 中也可用,並且同樣可以用於分配和釋放動態記憶體。

但請注意,由這些函式分配的記憶體塊不一定與 new 返回的記憶體塊相容,因此不應將它們混合使用;每種分配都應該用其自己的一套函式或運算子來處理。

Index
目錄