動態記憶體管理
在編寫程式時,通常並不知道需要處理的資料量,或者難以評估所需處理資料量的變動程度。在這種情況下,要達到有效的資源利用,必須在執行時動態地分配所需記憶體,並在使用完畢後儘早釋放不需要的記憶體,這就是
動態記憶體管理原理。
動態記憶體管理同時還具有一個優點:當程式在具有更多記憶體的系統上需要處理更多資料時,不需要重寫程式。標準庫提供以下四個函數用於動態記憶體管理:
(1) malloc()、calloc()
分配新的記憶體區域。
(2) realloc()
調整已分配的記憶體區域。
(3) free()
釋放已分配的記憶體區域。
上述所有函數都宣告在標頭檔案 stdlib.h 中。物件在記憶體中所佔空間的大小是以位元組數量為單位計算的。許多標頭檔案(包括 stdlib.h)專門定義了型別 size_t 用來儲存這種記憶體空間的相關資訊。例如,sizeof 運算子以型別 size_t 返回位元組數量。
動態記憶體分配
兩個記憶體分配函數 malloc()和 calloc()的引數不一樣:
void*malloc(size_t size);
函數 malloc()分配連續的記憶體區域,其大小不小於 size。當程式通過 malloc()獲得記憶體區域時,記憶體中的內容尚未決定。
void*calloc(size_t count,size_t size);
函數 calloc()分配一塊記憶體區域,其大小至少是 count_size。換句話說,上述語句分配的空間應足夠容納一個具有 count 個元素的陣列,每個元素占用 size 個位元組。而且,calloc()會把記憶體中每個位元組都初始化為 0。
兩個函數都返回 void 指標,這種指標被稱為無型別指標(typeless pointer)。返回指標的值是所分配記憶體區域中第一個位元組的地址,當
分配記憶體失敗時,返回空指標。
當程式將這個 void 指標賦值給不同型別的指標變數時,編譯器會隱式地進行相應的型別轉換。然而,一些程式設計師傾向於使用顯式型別轉換。當獲取所分配的記憶體位置時,所使用的指標型別決定了該如何翻譯該位置的資料。
下面是一些範例:
#include <stdlib.h> // 提供函數原型
typedef struct { long key;
/* ...其他成員... */
} Record; // 一個結構型別
float *myFunc( size_t n )
{
// 為一個double型別物件分配儲存空間
double *dPtr = malloc( sizeof(double) );
if ( dPtr == NULL ) // 記憶體不足
{
/* ...處理錯誤... */
return NULL;
}
else // 獲得記憶體:使用它
{
*dPtr = 0.07;
/* ... */
}
// 為兩個Record型別分配儲存空間
Record *rPtr;
if ( ( rPtr = malloc( 2 * sizeof(Record) ) == NULL )
{
/* ...處理記憶體不足錯誤... */
return NULL;
}
// 為一個具有n個float元素的陣列分配儲存空間
float *fPtr = malloc( n * sizeof(float) );
if ( fPtr == NULL )
{
/* ...處理錯誤... */
return NULL;
}
/* ... */
return fPtr;
}
將所分配的記憶體區域中每個位元組都初始化為 0,這種方式很有意義,它確保不只把分配給結構物件的記憶體都預設地初始化為 0,連成員之間的填補位置也是 0。
在這種情況下,函數 calloc()比 malloc()更有優勢,雖然在部分 C 語言實現版本中,它的執行效率不及後者。對於要分配的記憶體空間,函數calloc()的表示方式有所區別。可以把上述範例中的部分語句使用函數 calloc()重新編寫:
// 為一個double型別物件分配儲存空間
double *dPtr = calloc( 1, sizeof(double) );
// 為兩個Record型別分配儲存空間
Record *rPtr;
if ( ( rPtr = calloc( 2, sizeof(Record) ) == NULL )
{ /* ...處理記憶體不足錯誤... */ }
// 為一個具有n個float元素的陣列分配儲存空間
float *fPtr = calloc( n, sizeof(float));