malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。
1.malloc函數初探
首先我們要知道malloc是一個函數,malloc的全稱是memory allocation,中文叫動態內存分配。它的原型是:
void *malloc(int size);
- 1
說明:malloc 向系統申請分配指定size個字節的內存空間,返回類型是 void* 類型。void* 表示未確定類型的指針。C,C++規定,void* 類型可以強制轉換爲任何其它類型的指針。
在這裏注意:
(1) void* 表示未確定類型的指針,更明確的說是指申請內存空間時還不知道用戶是用這段空間來存儲什麼類型的數據(比如是char還是int或者…)
(2) 使用malloc向系統申請內存時可能分配失敗。如果分配失敗,則返回一個空指針(NULL)。關於分配失敗的原因,有很多種,比如說空間不足就是一種。
一個對應的釋放內存的函數:
void free(void *FirstByte);
- 1
該函數是將之前用malloc分配的空間還給程序或者是操作系統,也就是釋放了這塊內存,讓它重新得到自由。
至於用法,其實這兩個函數用起來倒不是很難,也就是malloc()之後覺得不用了需要釋放把它給free()了,舉個簡單例子:
char *Ptr = NULL;
Ptr = (char *)malloc(100 * sizeof(char));
if (NULL == Ptr){
exit (1);
}
gets(Ptr);
free(Ptr);
Ptr = NULL;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
當然,具體情況要具體分析以及具體解決。比如說,你定義了一個指針,在一個函數裏申請了一塊內存然後通過函數返回傳遞給這個指針,那麼也許釋放這塊內存這項工作就應該留給其他函數了。只要保證每個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;
- 1
這個new表達式在堆區中分配創建了一個整型對象,並返回此對象的地址,並用該地址初始化指針pi 。
3.2 動態創建對象的初始化
動態創建的對象可以用初始化變量的方式初始化。
int *pi=new int(100); //指針pi所指向的對象初始化爲100
string *ps=new string(10,'9');//*ps 爲“9999999999”
- 1
- 2
如果不提供顯示初始化,對於類類型,用該類的默認構造函數初始化;而內置類型的對象則無初始化。
也可以對動態創建的對象做值初始化:
int *pi=new int( );//初始化爲0
int *pi=new int;//pi 指向一個沒有初始化的int
string *ps=new string( );//初始化爲空字符串 (對於提供了默認構造函數的類類型,沒有必要對其對象進行值初始化)
- 1
- 2
- 3
3.3 撤銷動態創建的對象
delete表達式釋放指針指向的地址空間。
delete pi ;// 釋放單個對象
delete [ ]pi;//釋放數組
- 1
- 2
如果指針指向的不是new分配的內存地址,則使用delete是不合法的。
3.4 在delete之後,重設指針的值
delete p;
- 1
執行完該語句後,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中
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
(2) malloc 只管分配內存,並不能對所得的內存進行初始化,所以得到的一片新內存中,其值將是隨機的。new創建的對象可以用初始化變量的方式初始化。
除了分配及最後釋放的方法不一樣以外,通過malloc或new得到指針,在其它操作上保持一致。
因此對於
int *p;
p=malloc(sizeof(int));
C++中,程序無法通過編譯,報錯:“不能將 void* 賦值給 int * 類型變量”。所以必須通過
(int *) 來將強制轉換。