C語言動態記憶體管理:malloc、realloc、calloc以及free函式
我們已經掌握的記憶體開闢方式有:
int val = 20;//在棧空間上開闢四個位元組
char arr[10] = {0};//在棧空間上開闢10個位元組的連續空間
但是這種開闢空間的方式有兩個特點:
1. 空間開闢的大小是固定的。
2. 陣列在申明的時候,必須指定陣列的長度,它所需要的記憶體在編譯時分配。
但是我們在實際的需求中,對於空間的需求,不僅僅是上述的情況。有時候我們需要的空間大小在程式運行的時候才能知道,那陣列編譯時開闢空間的方式就不能滿足我們的需求了。
這時候我們就需要動態存開闢了。
動態記憶體函式介紹
1、malloc和free:C語言提供的一個動態記憶體開闢的函式
#include<stdlib.h>
void* malloc (size_t size);
1、這個函式向記憶體申請一塊連續可用的空間,並返回指向這塊空間的指標。
2、如果開闢成功,則返回一個指向開闢好空間的指標。
3、如果開闢失敗,則返回一個NULL指標,因此malloc的返回值一定要做檢查。
4、 返回值的型別是void * 。所以malloc函式並不知道開闢空間的型別,具體在使用的時候使用者自己來決定。
5、如果引數為0,malloc的行為是標準是未定義的,取決於編譯器。
free是C語言提供的一個專門用來釋放和回收我們申請的動態記憶體的
#include<stdlib.h>
void free (void *ptr);
1、 free函式用來釋放動態開闢的記憶體。
2、如果引數ptr指向的空間不是動態開闢的,那free函式的行為是未定義的。
3、如果引數是NULL指標,則函式什麼事都不做。
#include<stdio.h>
#include<stdlib.h>
int main()
{
//程式碼1
int num = 0;
scanf("%d", &num);
int arr[num] = {0};
//程式碼2
int *ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));
if(NULL != ptr)//判斷ptr指標是否為空
{
int i = 0;
for(i=0; i<num; i++)
{
*(ptr+i) = 0;
}
}
free(ptr);//釋放ptr所指向的動態記憶體
ptr = NULL;//是否有必要?
return 0;
}
free函式的作用是通過指標ptr把ptr所指向的記憶體空間釋放掉,並沒有把ptr指標釋放掉,所謂釋放掉就是將這塊記憶體中的物件銷燬,並把這塊記憶體交還給系統留作他用。指標ptr中的值仍是那塊記憶體的首地址,倘若此時這塊記憶體又被指派用於儲存其他的值,那麼對ptr進行解引用就可以訪問這個當前值,但如果這塊記憶體的狀態是不確定的,也許是受保護的,也許不儲存任何物件,這時如果對ptr解引用則可能出現執行時錯誤,並且這個錯誤檢測起來非常困難。所以為了安全起見,在free一個指標後,將這個指標設定為NULL或零指標常量。
2、calloc:也是C語言提供的用於動態記憶體開闢的函式,但是與剛剛講過的malloc有一點區別。
#include<stdlib.h>
void *calloc(size_t num,size_t size);
1、函式的功能是為num個大小為size的元素開闢一塊空間,並且把空間的每個位元組初始化為0。
2、與malloc函式的區別只在於會在返回地址之前把申請的空間的每個位元組初始化為0。
#include<stdio.h>
#include<stdlib.h>
int main()
{
//使用calloc函式為10個大小為4個位元組的元素開闢空間
int *p = calloc(10, sizeof(int));
if(NULL != p)
{
//開闢成功
//使用空間
}
//使用完畢釋放
free(p);
p = NULL;
return 0;
}
所以這個函式在我們需要對我們所開闢的空間進行相應的初始化時給我們帶來了非常大方便。只要呼叫這個函式,既開闢了我們想要的空間,也同時幫助我們完成了初始化的任務。
3、realloc:是c語言提供的另外一個用於動態記憶體開闢的函式,我想,其主要功能是幫我們解決malloc函式所帶來的不足,因為一旦我們使用malloc函式,使用完畢以後,所開闢的空間就已經確定下來,當然了,這一定是滿足我們當下的需求的,但是我們的需求很有可能會改變,即我們所需要的空間會改變,此時有了realloc函式就會讓我們的動態記憶體管理更加靈活。
1、 realloc函式的出現讓動態記憶體管理理更更加靈活。
2、有時會我們發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了得到合理的記憶體,我們一定會希望能夠對記憶體的大小做靈活的調整。那 realloc函式就可以做到幫助我們對動態開闢記憶體大小的調整。 函式原型如下:
#include<stdlib.h>
void* realloc (void* ptr, size_t size);
1、ptr 是要調整的記憶體地址
2、size 調整之後新⼤大⼩小 返回值為調整之後的記憶體起始位置。
3、這個函式調整原記憶體空間大小的基礎上,還會將原來記憶體中的資料移動到新的空間
4、realloc在調整記憶體空間的是存在兩種情況:
情況1:原有空間之後有足夠大的空間
情況2:原有空間之後沒有足夠大的空間
情況1
當是情況1 的時候,要擴充套件記憶體就直接在原有記憶體之後直接追加空間,原來空間的資料不不發生變 化。
情況2
當是情況2 的時候,原有空間之後沒有足夠多的空間時,擴充套件的方法是:在堆空間上另找一個合 適大小的連續空間來使用。這樣函式返回的是一個新的記憶體地址。
由於上述的兩種情況,realloc函式的使用就要注意一些。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int *ptr = malloc(100);
if(ptr != NULL)
{
//業務處理
}
else
{
//記憶體開闢失敗
exit(EXIT_FAILURE);
}
//現在我們需要更大的記憶體空間,接下來用realloc函式擴充套件容量
//程式碼1
ptr = realloc(ptr, 1000);//這樣可以嗎?(如果申請失敗會如何?)
//程式碼1:如果申請失敗返回一個空指標,而我們並不知道返回的是空指標
//(因為沒有對ptr進行判斷),那我們在之後的程式碼中
//可能要繼續對ptr所指向的記憶體進行訪問(即操作空指標)
//此時就會出現錯誤(後面說明)
//程式碼2
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
//說明此時記憶體調整成功
ptr = p;
}
//業務處理
free(ptr);
//釋放記憶體
ptr = NULL;
return 0;
}
常見的動態記憶體錯誤
1、對空指標的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就會有問題
free(p);
}
後果:導致非法訪問記憶體地址
2、對動態開闢空間的越界訪問
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//當i是10的時候越界訪問
}
free(p);
}
後果:malloc為我們開闢號空間以後,會自己 管理一個連結串列用來維護堆中的記憶體,由於malloc通過連結串列來維護,就必不可少的會利用空間來存放next指標域,這個next指標域就緊緊的挨在malloc分配的記憶體的後面。所以,如果越界訪問malloc分配的記憶體空間,就會破壞next域,從而破壞了連結串列結構,因此下一次再呼叫malloc分配記憶體時就會失敗。
3、對非動態開闢的記憶體使用free釋放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
後果:非法操作,程式中斷。
4、使用free釋放動態開闢的記憶體的其中一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不不再指向動態記憶體的起始位置
}
後果:編譯報錯
5、多次使用free釋放同一塊動態開闢的記憶體
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重複釋放
}
後果:重複 free() 在預設情況下都會導致 C 函式庫呼叫 abort() 終止程式。
6、忘記釋放動態開闢的記憶體
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
後果:造成記憶體洩漏。
切記:動態開闢的記憶體使用完畢以後一定要釋放,並且正確釋放。