柚子快報邀請碼778899分享:筆記 動態(tài)內(nèi)存管理<C語言>
柚子快報邀請碼778899分享:筆記 動態(tài)內(nèi)存管理<C語言>
導(dǎo)言
??????? 在C語言學(xué)習(xí)階段,指針、結(jié)構(gòu)體和動態(tài)內(nèi)存管理,是后期學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的最重要的三大知識模塊,也是C語言比較難的知識模塊,但是“天下無難事”,只要認(rèn)真踏實的學(xué)習(xí),也能解決,所以下文將介紹動態(tài)內(nèi)存管理涉及到的一些函數(shù)以及概念。
目錄
導(dǎo)言
為什么存在動態(tài)內(nèi)存管理
malloc和free
malloc
?free
calloc和realloc
calloc
?realloc
常見的關(guān)于動態(tài)內(nèi)存管理錯誤
1.對可能是NULL指針的引用
?2.對不是動態(tài)開辟的內(nèi)存進(jìn)行釋放
3.對動態(tài)開辟的內(nèi)存進(jìn)行越界訪問
4.使用free釋放動態(tài)開辟內(nèi)存的一部分
5.忘記內(nèi)存釋放(忘記free),造成內(nèi)存泄漏
例題
為什么存在動態(tài)內(nèi)存管理
int a;
char arr[10];
這是我們常用的用于向內(nèi)存申請空間的辦法,但是:
●空間開辟的空間是固定的
●數(shù)組在申明時,數(shù)組大小一旦確定,申請的內(nèi)存空間不可變
在實際編寫程序時,可能我們對于內(nèi)存空間的需求不是固定,那么使用動態(tài)內(nèi)存管理自己申請空間、自己釋放空間就是一個很好的選擇。
malloc和free
malloc
函數(shù)參數(shù)及其返回值
void* malloc(size_t size);
//申請size個字節(jié)的空間
//返回值,成功申請:返回開辟空間的首地址、失?。悍祷豊ULL
注意點
●返回值是void*,那么我們在實際使用時,應(yīng)把它強(qiáng)制轉(zhuǎn)化為我們需要的類型。
●與局部變量不同,開辟空間在堆區(qū)(如數(shù)組在棧區(qū))
●malloc不會將內(nèi)存空間初始化為0,這是與最大calloc區(qū)別!
●動態(tài)內(nèi)存可調(diào)整(通過realloc)
使用舉例:
?free
函數(shù)參數(shù)及其返回值
void free(void* ptr);
//釋放動態(tài)內(nèi)存申請的ptr指向的空間
注意點
●只能用來手動釋放動態(tài)申請的空間,如果不是結(jié)果是未定義的
●釋放空間后,只是將權(quán)限交還于操作系統(tǒng),指針還指向著地址(懸空指針),應(yīng)該手動將其置為NULL
●如果釋放指針是NULL,那么什么也不做。
?使用舉例:
#include
int main() {
int* ptr = NULL;
int count = 0;
scanf("%d", &count);
ptr = (int*)malloc(count * sizeof(int));
free(ptr);
ptr = NULL;
return 0;
}
calloc和realloc
calloc
函數(shù)參數(shù)及其返回值
void* calloc(size_t num,size_t size);
//申請num個size個字節(jié)的空間,并初始化為0
//返回值,成功申請:返回開辟空間的首地址、失?。悍祷豊ULL
注意點
●開辟空間并全部初始化為0
●兩個參數(shù)(num個size字節(jié))
●其他與malloc類似
使用舉例:
?realloc
?函數(shù)參數(shù)及其返回值
void* realloc(void* ptr,size_t size);
//ptr是要調(diào)整的內(nèi)存地址
//size是調(diào)整之后的大小
//返回值,成功申請:返回調(diào)整空間的首地址、失?。悍祷豊ULL
使用舉例:
#include
#include
int main() {
int count;
scanf("%d", &count);
int* ptr = (int*)calloc(count, sizeof(int));//申請count個int大小的空間
if (ptr) {//判斷是不是NULL:是否申請成功
for (int i = 0; i < count; i++)
ptr[i] = i;//賦值:從0開始到count-1步為1的序列
for (int i = 0; i < count; i++)
printf("%d ", ptr[i]);
}
printf("\n");
printf("調(diào)整前的地址:%p\n", ptr);//觀察動態(tài)(realloc)調(diào)整前的地址
int* p = (int*)realloc(ptr, (count + 5) * sizeof(int));
//申明一個新指針來接收,防止調(diào)整失敗返回NULL,數(shù)據(jù)丟失,調(diào)整為多5個int大小的地址
if (p)//判斷是否是NULL:是否調(diào)整成功
ptr = p;
printf("調(diào)整后的地址:%p", ptr);//觀察動態(tài)(realloc)調(diào)整后的地址
free(ptr);
ptr = NULL;
return 0;
}
運(yùn)行結(jié)果:
先開辟10個int字節(jié)大小空間的運(yùn)行結(jié)果:
?先開辟20個int字節(jié)大小空間的運(yùn)行結(jié)果:
注意點
●參數(shù)size為0時,返回值是NULL,并將ptr的內(nèi)存釋放,這是未定義的行為,在不同的編譯器上不能保證
●如果ptr參數(shù)為NULL,會動態(tài)開辟一個新的內(nèi)存空間,此時realloc函數(shù)的作用等同于malloc
●這個函數(shù)調(diào)整空間時會把數(shù)據(jù)移到新的內(nèi)存空間內(nèi)(實際上,有一種情況不會,但是為了代碼的健壯性和可移植性,我們最好這樣定義)
兩種情況:
①原有地址后面有足夠的空間容納調(diào)整后的空間
②原有地址后面沒有足夠的空間容納調(diào)整后的空間
其實在前面的使用舉例中我們已經(jīng)觀察到:
先開辟10個int字節(jié)大小空間的運(yùn)行結(jié)果(第一種情況)
????????直接在原地址后面開辟新空間
先開辟20個int字節(jié)大小空間的運(yùn)行結(jié)果(第二種情況)
??????? 找到一塊能容納調(diào)整后的空間的地址,將數(shù)據(jù)移動到其中
關(guān)于參數(shù)size為0時的舉例:
因為我們沒有辦法直接觀察一塊動態(tài)開辟的內(nèi)存是否被釋放,且這種size為0行為是未定義的,所以我們只能觀察它的返回值
?
?關(guān)于參數(shù)ptr是NULL時的情況
此時realloc等同malloc
注意:動態(tài)內(nèi)存管理的4個函數(shù)都包含在
常見的關(guān)于動態(tài)內(nèi)存管理錯誤
1.對可能是NULL指針的引用
?
?2.對不是動態(tài)開辟的內(nèi)存進(jìn)行釋放
// 2.對不是動態(tài)開辟的內(nèi)存進(jìn)行釋放
#include
#include
int main() {
int a = 0;
int* p = &a;
free(p);
p = NULL;
return 0;
}
3.對動態(tài)開辟的內(nèi)存進(jìn)行越界訪問
//3.對動態(tài)開辟的內(nèi)存進(jìn)行越界訪問
#include
int main() {
int* p = (int*)malloc(sizeof(int));
p++;
*p = 1;
free(p);
p = NULL;
return 0;
}
4.使用free釋放動態(tài)開辟內(nèi)存的一部分
//4.使用free釋放動態(tài)開辟內(nèi)存的一部分
#include
int main() {
int* p = (int*)malloc(4*sizeof(int));//動態(tài)開辟4個int大小的空間
p++;//指向第二個元素
free(p);
p = NULL;
return 0;
}
5.忘記內(nèi)存釋放(忘記free),造成內(nèi)存泄漏
//5.忘記內(nèi)存釋放(忘記free),造成內(nèi)存泄漏
#include
int main() {
int* p = (int*)malloc(sizeof(int));
return 0;
}
例題
1.
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main() {
Test();
return 0;
}運(yùn)行會咋樣
p雖然在GetMemory函數(shù)中開辟了內(nèi)存,但是在出函數(shù)時,該地址被銷毀,所以str還是NULL指針,對NULL指針進(jìn)行賦值是一個未定義行為。(傳值調(diào)用而沒有使用傳址調(diào)用)
改正(二級指針):
void GetMemory(char** p)//使用二級指針接收
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);//傳入指針的地址
strcpy(str, "hello world");
printf(str);
}
int main() {
Test();
return 0;
}
改正(將開辟的空間返回):
2.
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main() {
Test();
return 0;
}//運(yùn)行結(jié)果?
GetMemory函數(shù)返回了一個地址,但是這個地址出了函數(shù),權(quán)限已經(jīng)收回給了操作系統(tǒng),str接收的是一個野指針,并將它打印出來,這種行為是未定義的,可能造成錯誤。(說到底是??臻g返回會被銷毀的問題)
我們知道只要是函數(shù)內(nèi)的變量都是??臻g申請的空間,在出函數(shù)時,都會被回收,但是動態(tài)內(nèi)存管理申請的空間,必須要手動釋放,所以在函數(shù)中我們使用動態(tài)內(nèi)存申請的地址是不會被收回的(堆區(qū)申請),所以我們嘗試改正時,在函數(shù)內(nèi)部使用動態(tài)內(nèi)存申請,并返回。
改正:
char* GetMemory(void)
{
char* p = (char*)malloc(20);
strcpy(p, "hello world!");
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main() {
Test();
return 0;
}
3.
//3
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main() {
Test();
return 0;
}//運(yùn)行結(jié)果,以及問題
沒釋放空間,內(nèi)存泄漏
改正:
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
free(ptr);
ptr=NULL;
}
int main() {
Test();
return 0;
}
使用了已經(jīng)被釋放的內(nèi)存
柚子快報邀請碼778899分享:筆記 動態(tài)內(nèi)存管理<C語言>
推薦文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。