1. 程式人生 > >malloc函式詳解以及和new的區別

malloc函式詳解以及和new的區別

今天偶然看到一個面試經驗中提到malloc和new的區別,突然發現自己雖然兩個都用過,但是至於區別,真的不是很明白 ,所以就仔細查了一些資料,算是對這個點徹底地瞭解一下,現在把我所學到的記錄下來。

malloc與free是C++/C語言的標準庫函式,new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體。

1.malloc函式初探

首先我們要知道malloc是一個函式,malloc的全稱是memory allocation,中文叫動態記憶體分配。它的原型是:

void *malloc(int size);

說明:malloc 向系統申請分配指定size個位元組的記憶體空間,返回型別是 void* 型別。void* 表示未確定型別的指標。C,C++規定,void* 型別可以強制轉換為任何其它型別的指標。   
在這裡注意:
(1) void* 表示未確定型別的指標,更明確的說是指申請記憶體空間時還不知道使用者是用這段空間來儲存什麼型別的資料(比如是char還是int或者…)
(2) 使用malloc向系統申請記憶體時可能分配失敗。如果分配失敗,則返回一個空指標(NULL)。關於分配失敗的原因,有很多種,比如說空間不足就是一種。
一個對應的釋放記憶體的函式:

void free(void *FirstByte);

該函式是將之前用malloc分配的空間還給程式或者是作業系統,也就是釋放了這塊記憶體,讓它重新得到自由。
至於用法,其實這兩個函式用起來倒不是很難,也就是malloc()之後覺得不用了需要釋放把它給free()了,舉個簡單例子:

char *Ptr = NULL; 
Ptr = (char *)malloc(100 * sizeof(char)); 
if (NULL == Ptr){ 
        exit (1); 
} 
gets(Ptr);  
free(Ptr); 
Ptr = NULL; 

當然,具體情況要具體分析以及具體解決。比如說,你定義了一個指標,在一個函式裡申請了一塊記憶體然後通過函式返回傳遞給這個指標,那麼也許釋放這塊記憶體這項工作就應該留給其他函數了。只要保證每個malloc()之後必須有一個free()與之對應。

關於這個函式的用法需要注意的一些地方:

1.申請了記憶體空間後,必須檢查是否分配成功。

2.當不需要再使用申請的記憶體時,記得釋放;釋放後應該把指向這塊記憶體的指標指向NULL,防止程式後面不小心使用了它。

3.這兩個函式應該是配對。如果申請後不釋放就是記憶體洩露;如果無故釋放那就是什麼也沒有做。釋放只能一次,如果釋放兩次及兩次以上會出現錯誤(釋放空指標例外,釋放空指標其實也等於啥也沒做,所以釋放空指標釋放多少次都沒有問題)。

4.雖然malloc()函式的型別是void*,任何型別的指標都可以轉換成void*,但是最好還是在前面進行強制型別轉換,因為這樣可以躲過一些編譯器的檢查。

2.malloc函式深入

看了以上的內容我們大致知道malloc函式的初步內容以及它的用法,但是我們不知道malloc函式是怎麼實現動態分配記憶體的。那它是怎麼動態分配記憶體的呢?

答案是從堆裡面獲得空間。也就是說函式返回的指標是指向堆裡面的一塊記憶體。作業系統中有一個記錄空閒記憶體地址的連結串列。當作業系統收到程式的申請時,就會遍歷該連結串列,然後就尋找第一個空間大於所申請空間的堆結點,然後就將該結點從空閒結點連結串列中刪除,並將該結點的空間分配給程式。

說到這裡就不得不提堆,什麼是堆呢?堆是大家共有的空間,分全域性堆和區域性堆。全域性堆就是所有沒有分配的空間,區域性堆就是使用者分配的空間。堆在作業系統對程序 初始化的時候分配,執行過程中也可以向系統要額外的堆,記住一點:從堆申請的記憶體用完了要還給作業系統,也就是釋放,如果不還的話會發生記憶體洩露

而所謂記憶體洩露就好比你去食堂吃飯,你吃完了之後沒把盤子拿走,這在別人看來是這個座有人佔了,可怕的是你回頭吃飯又佔了一個新座,你吃完之後還是沒把盤子拿走……終於,整個食堂的座位都被佔滿了,食堂也亂套了。(比喻不太恰當,但大致是這個意思)

平常我們老說堆疊、堆疊,那棧又是什麼呢?棧是執行緒獨有的,儲存其執行狀態和區域性自動變數的。棧線上程開始的時候初始化,每個執行緒的棧互相獨立。每個函式都有自己的棧,棧被用來在函式之間傳遞引數。作業系統在切換執行緒的時候會自動的切換棧,就是切換SS/ESP暫存器。棧空間不需要在高階語言裡面顯式的分配和釋放。

通過上面對概念的描述,可以知道:

棧是由編譯器自動分配釋放,存放函式的引數值、區域性變數的值等。操作方式類似於資料結構中的棧。

堆一般由程式設計師分配釋放,若不釋放,程式結束時可能由作業系統回收。注意這裡說是可能,並非一定。所以堆一定要釋放!

3.new運算子

3.1 C++中,用new和delete動態建立和釋放陣列或單個物件。
動態建立物件時,只需指定其資料型別,而不必為該物件命名,new表示式返回指向該新建立物件的指標,我們可以通過指標來訪問此物件。

int *pi=new int;

這個new表示式在堆區中分配建立了一個整型物件,並返回此物件的地址,並用該地址初始化指標pi 。

3.2 動態建立物件的初始化

動態建立的物件可以用初始化變數的方式初始化。

int *pi=new int(100); //指標pi所指向的物件初始化為100
string *ps=new string(10,'9');//*ps 為“9999999999”

如果不提供顯示初始化,對於類型別,用該類的預設建構函式初始化;而內建型別的物件則無初始化。
也可以對動態建立的物件做值初始化:

int *pi=new int( );//初始化為0
int *pi=new int;//pi 指向一個沒有初始化的int
string *ps=new string( );//初始化為空字串 (對於提供了預設建構函式的類型別,沒有必要對其物件進行值初始化)

3.3 撤銷動態建立的物件
delete表示式釋放指標指向的地址空間。

delete pi ;// 釋放單個物件
delete [ ]pi;//釋放陣列

如果指標指向的不是new分配的記憶體地址,則使用delete是不合法的。

3.4 在delete之後,重設指標的值

delete p; 

執行完該語句後,p變成了不確定的指標,在很多機器上,儘管p值沒有明確定義,但仍然存放了它之前所指物件的地址,然後p所指向的記憶體已經被釋放了,所以p不再有效。此時,該指標變成了懸垂指標(懸垂指標指向曾經存放物件的記憶體,但該物件已經不存在了)。懸垂指標往往導致程式錯誤,而且很難檢測出來。
一旦刪除了指標所指的物件,立即將指標置為0,這樣就非常清楚的指明指標不再指向任何物件。(零值指標:int *ip=0;

3.5 區分零值指標和NULL指標

零值指標,是值是0的指標,可以是任何一種指標型別,可以是通用變體型別void*也可以是char*,int*等等。
空指標,其實空指標只是一種程式設計概念,就如一個容器可能有空和非空兩種基本狀態,而在非空時可能裡面儲存了一個數值是0,因此空指標是人為認為的指標不提供任何地址訊息。

4.malloc和new的區別

(1) new 返回指定型別的指標,並且可以自動計算所需要大小。而 malloc 則必須要由我們計算位元組數,並且在返回後強行轉換為實際型別的指標。
例:

//new
int *p;   
p = new int; //返回型別為int* 型別(整數型指標),分配大小為 sizeof(int); 
int* parr;   
parr = new int [100]; //返回型別為 int* 型別(整數型指標),分配大小為sizeof(int) * 100;

//malloc
int* p;   
p = (int *) malloc (sizeof(int)*128);//分配128個(可根據實際需要替換該數值)整型儲存單元,並將這128個連續的整型儲存單元的首地址儲存到指標變數p中  
double *pd=(double *) malloc (sizeof(double)*12);//分配12個double型儲存單元,並將首地址儲存到指標變數pd中

(2) malloc 只管分配記憶體,並不能對所得的記憶體進行初始化,所以得到的一片新記憶體中,其值將是隨機的。new建立的物件可以用初始化變數的方式初始化。

除了分配及最後釋放的方法不一樣以外,通過malloc或new得到指標,在其它操作上保持一致。

5.參考資料