內存管理對所有程序來說都很重要。有時候內存由運行時系統隱式的管理,比如為變量自動分配內存。在這種情況下,變量分配在它所處的函數的棧幀上(每個函數都有它自己的棧幀,用來保存它的局部變量和返回地址等)。如果是靜態或全局變量,內存處於程序的數據段,會被自動清零。數據段是一個區別於可執行代碼和運行時系統管理的其它數據的內存區域。
C語言也支持動態內存管理,對象就是從堆上分配出來的內存。這是用分配和釋放函數手動實現的,這個過程被稱為動態內存管理。在C中動態分配內存的基本步驟有:
1、用malloc類的函數分配內存;
2、用這些內存支持應用程序;
3、用free函數釋放內存。
int* pi = (int*) malloc (sizeof(int)); *pi = 5; printf(" pi is : %d\n address : %p\n", *pi, pi); free(pi); // pi is : 5 // address : 0x8843008
malloc函數的參數指定要分配的字節數。如果成功則返回從堆上分配的內存的指針,如果失敗則返回空指針。sizeof操作符能確定在宿主系統中應該分配的正確字節數,因為在不同的系統中,數據的字節數可能是不同的。每次調用malloc或類似函數時,結尾都應有對應的free函數調用,以防止內存洩露。
如果不再使用已分配的內存卻沒有將其釋放就會發生內存洩露,導致內存洩露的情況可能如下:
1、丟失內存地址;
2、應該調用free函數卻沒有調用(隱式洩露);
以下幾個內存分配函數可以用來管理動態內存,大部分包含在stdlib.h頭文件中:
1、malloc:從堆上分配內存
2、realloc:在之前分配的內存塊的基礎上,將內存重新分配為更大或更小的部分
3、calloc:從堆上分配內存並清零
4、free:將內存塊返回堆
動態內存從堆上分配,對於一連串內存分配調用,系統不保證內存的順序和所分配內存的連續性。不過,分配的內存會根據指針的數據類型對齊,比如4字節的整數會分配在能被4整除的地址邊界上。堆管理器返回的地址是最低字節的地址。
malloc函數從堆上分配一塊內存,分配的字節數由唯一的參數制定,返回值是void指針,如果內存不足,則返回NULL。此函數不會清空或修改內存,所以新分配的內存包含垃圾數據。
void* malloc(size_t);
執行malloc函數會執行以下操作:
1、從堆上分配內存;
2、內存不會被修改或者清空;
3、返回首字節的地址;
void指針可以轉換成任意指針,但是顯示的指針類型轉換能獲得對c++的更好兼容性,也能使代碼更清晰。注意函數參數盡量使用sizeof操作符,而且不要試圖接引一個未初始化內存數據的指針變量。
int* mpi = (int*) malloc (sizeof(int));
if(mpi!=NULL) { //pointer OK } else
{ //pointer not OK }
初始化靜態或全局變量時不能調用動態內存分配函數。但是對於靜態變量,可以用一個單獨的賦值語句來給變量分配內存。
static int* spi; spi = (int*) malloc (sizeof(int));
使用calloc函數會在分配的同時清空內存。清空內存意思是將其內容置為二進制的0。
void* calloc(size_t numElements, size_t elementSize);
calloc函數根據兩個參數的乘積來分配內存,並返回指向內存的第一個字節的指針。如果不能分配內存,則會返回NULL。執行calloc可能比malloc慢,因為它要做額外的內存清理工作。
如果需要增加或減少為指針分配的內存,可以使用realloc函數。
void* realloc(void* ptr, size_t size);
第一個參數是指向原內存快的指針,第二個是請求的大小。返回值是指向重新分配的內存的指針。如果請求的新內存大小比原來的小,多余的內存會返還給堆。如果比原來的大,可能的話,會緊挨著當前分配的內存區域分配新的內存,否則會在堆的其它區域分配並把就的內存復制到新區域。如果請求大小是0,那麼就釋放內存。如果無法分配,則返回空指針。注意永遠不要使用超出內存分配的地址。
使用free函數將不再使用的內存返還給系統。
void free(void* ptr);
指針參數應該指向由malloc函數分配的內存,這塊內存會被返還給堆。盡管該指針仍然指向該內存,但是該內存現在被認為包含垃圾數據,並且可能被寫入新的數據。該指針則稱為迷途指針。注意空指針不指向任何東西,迷途指針還是指向一個地址。如果是在一個函數裡面分配的內存,那麼就應該在同一個函數裡面釋放它。
重復釋放是指兩次釋放同一塊內存。第二次調用free會引發運行時異常。兩個指針指向同一個內存地址稱為別名。別名可能導致重復釋放。
堆管理器不一定會在free函數後將內存返回給操作系統,而是可能給程序後續使用。
迷途指針是指訪問已釋放的內存的指針。使用迷途指針會導致不可預期的行為和潛在的安全隱患。在指針別名的情況下,迷途指針更難以察覺。
int *gpi; { int tmp = 5; *gpi = &tmp; }
大部分編譯器把塊語句當做一個棧幀。在塊語句退出是,分配在棧幀上的tmp會出棧,gpi變成迷途指針,指向一塊已經被自動回收的內存。
除了使用函數手動分配內存外,還有一些非標准技術可以用來實現C的動態內存管理。這些技術的關鍵特性在於自動釋放內存。內存不再使用之後會被收集起來備用,釋放的內存成為垃圾,因此這個過程也叫垃圾回收。垃圾回收使程序員不必費心考慮何時釋放內存,更專注程序本身的問題。垃圾回收技術包括RAII和異常處理等。