8)“我想用malloc”、“我用不好malloc”
來看看一個變態程式:
/* xx.c:xx模組實現檔案 */
int *pInt;
/* xx模組的初始化函數 */
xx_intial()
{
pInt = ( int * ) malloc ( sizeof( int ) );
...
}
/* xx模組的其他函數(僅為舉例)*/
xx_otherFunction()
{
*Int = 10;
...
}
這個程式定義了一個全域整型變數指標,在xx模組的初始化函數中對此指標動態申請記憶體,並將pInt指向該記憶體首地址,並在xx模組的其他函數中都使用pInt指標對其指向的整數進行讀取和賦值。
這個程式讓我痛不欲生了好多天,扼腕歎息!這是我母校電腦系一位碩士的作品!作者為了用上malloc,拚命地把本來應該用一個全域整型變數擺平的程式活活弄成一個全域整型指標並在初始化函數中“動態”申請記憶體,自作聰明而正好暴露自己的無知!我再也不要見到這樣的程式。
那麼malloc究竟應該怎麼用?筆者給出如下規則:
規則1 不要為了用malloc而用malloc,malloc不是目的,而是手段;
規則2 malloc的真正內涵體現在“動態”申請,如果程式的特性不需動態申請,請不要用malloc;
上面列舉的變態程式完全不具備需要動態申請的特質,應該改為:
/* xx.c:xx模組實現檔案 */
int example;
/* xx模組的初始化函數 */
xx_intial()
{
...
}
/* xx模組的其他函數(僅為舉例) */
xx_otherFunction()
{
example = 10;
...
}
規則3 什麼樣的程式具備需要動態申請記憶體的特質呢?包含兩種情況:
(1)不知道有多少要來,來了的又走了
不明白?這麼說吧,譬如你正在處理一個報文隊列,收到的報文你都存入該隊列,處理完隊列頭的報文後你需要取出隊列頭的元素。
你不知道有多少報文來(因而你不知道應該用多大的報文數組),這些來的報文處理完後都要走(釋放),這種情況適合用malloc和free。
(2)慢慢地長大
譬如你在資源受限的系統中編寫一文字編輯器程式,你怎麼做,你需要這樣定義數組嗎?
char str[10000];
不,你完全不應該這麼做。即使你定義了一個10000位元組大的字串,使用者如果輸入10001個字元你的程式就完完了。
這個時候適合用malloc,因為你根本就不知道使用者會輸入多少字元,文本在慢慢長大,因而你也應慢慢地申請記憶體,用一個隊列把字串存放起來。
那麼是不是應該這樣定義資料結構並在使用者每輸入一個字元的情況下malloc一個CharQueue空間呢?
typedef struct tagCharQueue
{
char ch;
struct tagCharQueue *next;
}CharQueue;
不,這樣做也不對!這將使每個字元佔據“1+指標長度”的開銷。
正確的做法是:
typedef struct tagCharQueue
{
char str[100];
struct tagCharQueue *next;
}CharQueue;
讓字元以100為單位慢慢地走,當輸入字元數達到100的整數倍時,申請一片CharQueue空間。
規則4 malloc與free要成對出現
它們是一對恩愛夫妻,malloc少了free就必然會慢慢地死掉。成對出現不僅體現在有多少個malloc就應該有多少個free,還體現在它們應盡量出現在同一函數裡,“誰申請,就由誰釋放”,看下面的程式:
char * func(void)
{
char *p;
p = (char *)malloc(…);
if(p!=NULL)
…; /* 一系列針對p的操作 */
return p;
}
/*在某處調用func(),用完func中動態申請的記憶體後將其free*/
char *q = func();
…
free(q);
上述代碼違反了malloc和free的“誰申請,就由誰釋放”原則,代碼的耦合度大,使用者在調用func函數時需確切知道其內部細節!正確的做法是:
/* 在調用處申請記憶體,並傳入func函數 */
char *p=malloc(…);
if(p!=NULL)
{
func(p);
…
free(p);
p=NULL;
}
/* 函數func則接收參數p */
void func(char *p)
{
… /* 一系列針對p的操作 */
}
規則5 free後一定要置指標為NULL,防止其成為“野”指標