malloc、calloc、realloc和free
C 語言中開辟動態內存的有三個函數,分別為 malloc,calloc,realloc,釋放內存的只有一個函數 free。
realloc的使用是最容易犯錯的,在寫這篇博客前老師讓我把realloc的使用和注意事項仔細搜資料看看,在網上看了幾篇博客之后還是決定寫一篇博客,把這些東西都記錄下來。
測試程序是否有內存泄漏推薦使用開源工具 vld,vld的安裝和使用請點擊此處跳轉到vld教程博客
1.malloc的使用
malloc 使用的最頻繁,因為它最簡單,只需要一個參數,即需要動態開辟的內存的字節數,如果堆里的連續空間能滿足需要則將分配好的內存首地址返回,否則返回 NULL。
malloc()函數的實質,它可以將可用的內存塊連接成一個長長的鏈表,被稱為空閑鏈表,當用戶調用malloc函數時,它沿著鏈表找到比用戶申請的內存的可用的內存塊,如果內存塊大了,它會把可用的內存一分為二,其中一塊就是和用戶申請的內存大小相等的內存塊,它將這塊內存塊分配給用戶(即將內存塊首地址傳給用戶),另一塊會返回到鏈表上。
調用free函數的時候,它會將用戶釋放的內存重新連接在鏈表上,注意,此時并沒有和分隔開的第二塊內存合并。最后空閑鏈表連接的內存可能是一小塊一小塊的,此時如果用戶申請一個較大的內存塊,那么這時候malloc函數就會找當前空閑內存,如果當前空閑內存無法滿足用戶申請的空間的大小,那么malloc就會將相鄰的小內存塊合并,如果最后將空閑的相鄰的小內存塊合并也無法滿足用戶申請的空間大小的要求,那么malloc函數就會返回NULL。
當我們用malloc申請空間時比如我們申請4字節大小空間,其實malloc內部,申請的空間要大于這個字節數。malloc需要在用戶申請的空間的上部和下部多開辟4字節空間,作為上越界標記,和下越界標記,另外頭部還有28字節空間用來記錄空間使用情況等等,這些都是使用malloc的額外負擔。
2.calloc的使用
函數原型:
void* calloc(int n, int size);
n是元素的數目
size是每個元素的大小
功能:在內存動態存儲區中分配n塊長度為size字節的連續區域calloc 只是在 malloc 的基礎上將分配好的每個字節賦值為 0,這個功能使用并不多見,但由于 callloc 需要提供兩個參數,相比較而言并沒有 malloc 使用的多。
3.free的使用
free,用于釋放內存,主要注意 free 可能引發程序崩潰的幾個原因:
1、越界;
2,移動指針的指向,free 時指針不指向動態內存的開頭;
3、重復釋放同一段內存;
4、釋放不是動態創建的內存。
4.realloc的使用
realloc原型:
void* realloc(void* ptr,unsigned int newsize);
功能:改變ptr所指向的內存區域的大小為newsize的長度,并且將原內存中的數據拷貝到新內存中。
4.1realloc擴大內存的方式
我們都知道realloc是從堆上申請空間,當擴大一塊內存空間時,realloc會先試圖直接從現申請的堆空間后面進行擴充,如果現申請的堆空間后面有空間足夠用于擴充,那么皆大歡喜,realloc直接在堆后面進行擴充。
但是如果堆后面的空間不夠realloc擴充用呢?那么realloc就會在堆中重新找一塊newsize大小的內存,然后將原內存的數據拷貝到新內存中,指針指向新內存。這說明一個問題,就是realloc擴充空間過后,原來指向內存的指針的指向很可能會有變化。
如果ptr為NULL,則realloc和malloc一樣,分配一個newsize的空間,返回指向該空間首地址的指針。如果newsize大小為0,那么釋放ptr所指向的內存,返回NULL。如果沒有足夠的可用的內存用來擴充,那么返回NULL,ptr指向的內存和數據都不改變。
我們通過對指針是否指向同一個地址的判斷,來展現我們所說的現象。
下圖這種情況屬于直接在對后面空間擴充,導致p和q都指向堆空間。注意在這里我們沒有對空間的釋放,程序結束,空間自動釋放,這里可能會出現懸掛指針的問題(即空間被釋放,但是指針仍然指向這個空間),關于指針和空間釋放的問題,我們在最下面再給出解決。這里只是說明realloc擴容的方式和對應的現象。
如下程序,我們只擴充了4字節,realloc發現已申請的堆空間后面有足夠的空間,于是在堆后面緊接著擴充了4個字節。擴充后的空間的首地址和擴充前的空間的首地址一樣,所以p和q指向了同一個空間。
#include<stdio.h> #include<stdlib.h>int main() {int* p = (int*)malloc(sizeof(int) * 10);int* q = (int*)realloc(p, sizeof(int) * 11);if (q == NULL){printf("realloc false\n");}if (q == p){printf("p and q point the same space\n");}else{printf("realloc find a new space\n");}return 0; }
下面這種情況就屬于重新分配的空間,我們擴充的空間較大,realloc發現堆后面的空間不夠擴容,就重新申請了一個newsize大小的空間,q指向新空間首地址,p指向的空間被釋放,成為懸掛指針。
下面這種情況屬于擴充失敗,但是p指向的空間和數據都還存在,都不變都正常使用,realloc返回NULL,q == NULL。
#include<stdio.h> #include<stdlib.h>int main() {int* p = (int*)malloc(sizeof(int) * 10);int* q = (int*)realloc(p, sizeof(int) * 1024*1024*500);if (q == NULL){printf("realloc false\n");}else if (q == p){printf("p and q point the same space\n");}else{printf("realloc find a new space\n");}return 0; }4.2指針在 realloc時正確的使用方式
在討論realloc中我們已經知道了realloc的擴容方式,以及指針存在的問題,對上面代碼中的指針而言:
(1)如果擴容成功,p指針指向的堆空間將被釋放,p指針不處理的話,將會成為懸掛指針。
(2)如果擴容失敗,q指針將為NULL,p指針指向的空間和數據內容不變。
所以,我們要對指針做一些處理,如果擴容成功,我們將p指針指向NULL;如果擴容失敗,我們不能盲目的將p指針指向NULL,否則的話,我們的空間會無法釋放,數據會丟失。
這樣寫就會解決掉懸掛指針和空間和數據可能會的情況。但是這樣做不一定妥當,因為我們不知道是否空間申請成功了,所以我們如果一定要知道是否擴容成功,可以加斷言判斷等。但是一定要考慮懸掛指針問題,和空間和數據小心丟失問題,就沒問題了。
4.3realloc縮小內存的使用
我們在realloc擴容方式中提出可能在原堆空間后面擴容,也可能重新申請堆空間,重新申請堆空間的話,空間首地址會發生改變。在這里不僅有人要想了,那擴容是因為可能尋找新的堆空間才導致空間首地址會發生變化,那么realloc縮小內存,不需要尋找新空間,直接在原來申請的堆空間上將后面的空間釋放掉一部分就可以了,那是不是縮小內存的話空間首地址就一定不會改變了?
答案并非如此!
realloc可以在任何調用上移動內存,在許多實現中,收縮只會導致堆中保留大小的更改,并且不會移動內存。然而,在針對低碎片進行優化的堆中,它可以選擇將儲存器移動到更合適的位置。
所以,對于任何操作,都不要依賴realloc保持內存在同一個地方!
另外,realloc縮小內存的話,如果realloc(void* ptr,unsigned int newsize)中的newsize為0的話,那么ptr指向的空間被釋放,realloc返回NULL。如果只是簡單的縮小內存,那么原內存中超過newsize大小的空間中的數據會丟失。
總結
以上是生活随笔為你收集整理的malloc、calloc、realloc和free的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 串匹配算法——BF算法
- 下一篇: 递归与分治——子集问题