使用c語言輕松實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管
前言
我們已經(jīng)掌握的內(nèi)存開辟的方法有兩種
int a = 10; //在??臻g上開辟4個(gè)字節(jié)的空間
int a[10] = {0}; //在??臻g上開辟40個(gè)字節(jié)的連續(xù)空間
這些開辟方式都有兩個(gè)共同的特點(diǎn):
1.空間開辟大小是固定的
2.數(shù)組在申明的時(shí)候,必須指定數(shù)組的長度,它需要的內(nèi)存在編譯的時(shí)候分配
我們?yōu)槭裁匆獙?shí)現(xiàn)動(dòng)態(tài)管理內(nèi)存呢,這又什么作用呢?
我們對(duì)于空間的需求不僅僅只是上面兩種,有時(shí)候我們到底需要多少空間,需要運(yùn)行之后才能知道,這個(gè)時(shí)候就需要?jiǎng)討B(tài)開辟內(nèi)存空間,即動(dòng)態(tài)內(nèi)存函數(shù)就誕生了!
動(dòng)態(tài)內(nèi)存函數(shù)有那些?
1.malloc和free
2.calloc
3.realloc
malloc和free
malloc是C語言提供的一個(gè)動(dòng)態(tài)內(nèi)存開辟的函數(shù):
這個(gè)函數(shù)向內(nèi)存申請(qǐng)一塊連續(xù)可用的空間,并返回指向這塊空間的指針。
1.如果開辟成功,則返回一個(gè)指向開辟好空間的指針。
2.如果開辟失敗,則返回一個(gè) NULL 指針,因此 malloc 的返回值一定要做檢查。
3.返回值的類型是 void* ,所以 malloc 函數(shù)并不知道開辟空間的類型,具體在使用的時(shí)候使用者自己來決定。
4.如果參數(shù) size 為 0 , malloc 的行為是標(biāo)準(zhǔn)是未定義的,取決于編譯器。
void*的返回類型,使用的時(shí)候根據(jù)情況強(qiáng)制類型轉(zhuǎn)換
C語言還提供free函數(shù),專門是用于做動(dòng)態(tài)內(nèi)存的釋放和回收的,函數(shù)原型如下:
free函數(shù)是用來釋放動(dòng)態(tài)開辟的內(nèi)存:
1.如果參數(shù) ptr 指向的空間不是動(dòng)態(tài)開辟的,那free函數(shù)的行為是未定義的。(會(huì)報(bào)錯(cuò))
2.如果參數(shù) ptr 是NULL指針,則函數(shù)什么事都不做。
圖文演示:
頭文件要加上 malloc.h
代碼演示:
int main() { int num = 0; scanf("%d", &num); //int arr[num] = { 0 }; num 在 [] 中 //VS 不支持這樣,但是可以使用動(dòng)態(tài)內(nèi)存函數(shù),實(shí)現(xiàn)動(dòng)態(tài)數(shù)組 int* ptr = (int*)malloc(sizeof(int) * num); if (NULL == ptr) {//進(jìn)行判斷是否創(chuàng)建成功 perror("malloc::ptr"); } else { for (int i = 0; i < 10; i++) { *(ptr + i) = i; } for (int i = 0; i < 10; i++) { printf("%d ", *(ptr + i)); } free(ptr); //使用free函數(shù)釋放動(dòng)態(tài)申請(qǐng)的ptr ptr = NULL; //將ptr free之后,置為NULL,防止野指針非法訪問 } return 0; }
而且malloc函數(shù)創(chuàng)建的空間不會(huì)進(jìn)行初始化,里面存放的是隨機(jī)值,如圖
1.2 calloc
calloc函數(shù)也是C語言提供的,用來動(dòng)態(tài)內(nèi)存分配,原型如下:
calloc函數(shù)介紹:
1.函數(shù)的功能是為 num 個(gè)大小為 size 的元素開辟一塊空間,并且把空間的每個(gè)字節(jié)初始化為 0。
2.與函數(shù) malloc 的區(qū)別只在于 calloc 會(huì)在返回地址之前把申請(qǐng)的空間的每個(gè)字節(jié)初始化為全 0。
實(shí)操圖文分析:
代碼演示:
int main() { int num = 0; int* ptr = (int*)calloc(10, sizeof(int));//使用calloc函數(shù) for (int i = 0; i < 10; i++) { *(ptr + i) = i; } for (int i = 0; i < 10; i++) { printf("%d ", *(ptr + i)); } free(ptr);//free 動(dòng)態(tài)申請(qǐng)的ptr ptr = NULL;//置為NULL,防止野指針越界訪問 return 0; }
對(duì)于calloc動(dòng)態(tài)申請(qǐng)的空間是否每一個(gè)字節(jié)都變?yōu)?呢?我們來看下圖
這也是calloc和malloc函數(shù)的最大的區(qū)別,是否自動(dòng)初始化,前者有,后者無
realloc
realloc也是C語言提供的動(dòng)態(tài)內(nèi)存申請(qǐng)函數(shù),使得動(dòng)態(tài)內(nèi)存管理更加靈活。
本質(zhì)是可以對(duì)已經(jīng)動(dòng)態(tài)申請(qǐng)過的空間進(jìn)行增容,是更加靈活的。
有時(shí)會(huì)我們發(fā)現(xiàn)過去申請(qǐng)的空間太小了,有時(shí)候我們又會(huì)覺得申請(qǐng)的空間過大了,那為了合理的時(shí) 候內(nèi)存,我們一定會(huì)對(duì)內(nèi)存的大小做靈活的調(diào)整。那 realloc 函數(shù)就可以做到對(duì)動(dòng)態(tài)開辟內(nèi)存大小 的調(diào)整。
函數(shù)原型如下,并對(duì)兩個(gè)形參ptr和size進(jìn)行分析:
如上圖:
1.ptr可以為NULL,相當(dāng)于malloc一個(gè)新的空間,ptr是要調(diào)整的內(nèi)存地址
2.size同樣可以為0,則返回值取決于特定的庫實(shí)現(xiàn):它可能是空指針,也可能是不應(yīng)取消引用的其他位置。size是調(diào)整之后的大小
3.返回值為調(diào)整之后的內(nèi)存起始位置。
4.這個(gè)函數(shù)調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,還會(huì)將原來的數(shù)據(jù)移動(dòng)到新空間。
realloc調(diào)整內(nèi)存空間的時(shí)候有兩種情況:
第一種情況:當(dāng)原有空間之后的內(nèi)存空間足夠的時(shí)候
第二種情況:當(dāng)原有空間之后的內(nèi)存空間不夠時(shí)
如圖所示:
因?yàn)檫@兩種情況是隨機(jī)發(fā)生的,不能控制必須使用哪一種,所以我們就要小心一個(gè)事情,不要用原來動(dòng)態(tài)開辟的變量ptr來直接接收realloc,應(yīng)該創(chuàng)建臨時(shí)變量接收,先判空,之后再賦值給ptr
代碼圖示:
可以自行測試:
int main() { int* p = (int*)malloc(sizeof(int)*10); if (p == NULL) { perror("malloc::p"); } else { printf("%p\n", p); } int* ptr = (int*)realloc(p, sizeof(int) * 20);//創(chuàng)建臨時(shí)變量 //如果使用 int* p = (int*)realloc(p,....這樣的話如果創(chuàng)建失敗,返回NULL, //這樣的話p的內(nèi)容就沒有了,所以創(chuàng)建臨時(shí)變量ptr,然后下面判空之后可以交換 if (NULL == ptr) { perror("realloc::ptr"); } else { p = ptr; ptr = NULL; printf("%p\n", p); } free(p); p = NULL; return 0; }
常見動(dòng)態(tài)內(nèi)存錯(cuò)誤(案例分析)
對(duì)于NULL指針的解引用操作
意思就是要學(xué)會(huì)使用動(dòng)態(tài)內(nèi)存函數(shù)的時(shí)候嗎,要進(jìn)行判空,不然誰知道有沒有問題NULL
int main() { int* p = (int*)malloc(sizeof(int) * 10); *p = 10;//這個(gè)時(shí)候誰知道p是不是NULL,如果是NULL,那么這就是非法訪問,是錯(cuò)誤 free(p); return 0; }
對(duì)動(dòng)態(tài)開辟空間的越界訪問
就是說,開辟多少空間就是多少空間,不能越過這個(gè)字節(jié)數(shù)的界限訪問空間外的地址
int main() { int* p = (int*)malloc(sizeof(int) * 10); if (NULL == p) { perror("malloc::p"); } else { for (int i = 0; i < 100; i++) { *(p + i) = i + 1;//當(dāng)i等于10的時(shí)候就開始越界訪問 } for (int i = 0; i < 11; i++) { printf("%d ", *(p + i)); } free(p); p = NULL; } return 0; }
和數(shù)組一樣,不要越界,不需要多想什么額外的東西
對(duì)非動(dòng)態(tài)開辟內(nèi)存使用free釋放
free可以放置NULL進(jìn)去,不會(huì)報(bào)錯(cuò),但是不能放非動(dòng)態(tài)開辟的內(nèi)存,會(huì)報(bào)錯(cuò)
圖示分析free函數(shù):
代碼演示:
int main() { int* p = (int*)malloc(sizeof(int) * 10); int a = 10; free(&a);//非動(dòng)態(tài)內(nèi)存開辟的,會(huì)報(bào)錯(cuò) //free(NULL); //沒有什么反應(yīng),程序正常 return 0; }
使用free釋放了動(dòng)態(tài)開辟內(nèi)存的一部分
就是說如果動(dòng)態(tài)開辟內(nèi)存之后的p指針的位置發(fā)生改變的話再去釋放free(p)只是釋放一部分
代碼演示:
//舉例 int main() { int* p = (int*)malloc(sizeof(int) * 10); p++; free(p);//這個(gè)的時(shí)候p向右移動(dòng)一個(gè)整型字節(jié)空間,再進(jìn)行釋放,那么先前那個(gè)空間就沒被釋放 return 0; }
對(duì)同一塊動(dòng)態(tài)內(nèi)存進(jìn)行多次釋放
多次釋放會(huì)報(bào)錯(cuò)的
圖示:
動(dòng)態(tài)開辟空間忘記釋放(內(nèi)存泄漏)
所以我們要養(yǎng)成當(dāng)一個(gè)動(dòng)態(tài)空間不用的時(shí)候就free他,放置內(nèi)存泄露
代碼演示:
int main() { //test(); while (1) { malloc(1);//一直申請(qǐng)就是不釋放 } }
練習(xí)題
第一個(gè)
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); //改為傳遞地址就可以或者就是用str接收 //str= GetMemory(str);//實(shí)際上用臨時(shí)變量接收更好 strcpy(str, "hello world"); printf(str); //用完釋放 //free(str); //str=NULL; }
1.傳值操作,就算p申請(qǐng)了空間也不會(huì)使得str發(fā)生改變,所以str依舊是NULL,不能有strcpy
2.內(nèi)存泄漏, GetMemory(str);未釋放p的空間
第二個(gè)
char *GetMemory(void) { //修改為: //static char p[] = "hello world"; char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); }
典型的返回棧地址問題,p數(shù)組是局部變量 ,確實(shí)是返回了p的地址給str,但是GetMemory函數(shù)結(jié)束之后,數(shù)組p的空間就沒有,再訪問p的地址(printf(str))就會(huì)非法訪問
第三個(gè)
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(str); //str=NULL; }
沒有釋放str動(dòng)態(tài)開辟的空間,沒有free(str),str=NULL
第四個(gè)
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(str); //str=NULL; }
在使用str之前就釋放了str申請(qǐng)的空間,釋放之后str!=NULL,保留原來地址,str這個(gè)時(shí)候已經(jīng)是野指針了(因?yàn)闆]有了對(duì)相應(yīng)空間的訪問權(quán)限),之后確實(shí)是輸出了world,但是從if語句就已經(jīng)錯(cuò)誤了,置為str=NULL 就可以了
總結(jié)
本文主要是對(duì)于malloc、calloc、realloc、free函數(shù)的介紹和使用細(xì)節(jié)的說明,還有一些關(guān)于動(dòng)態(tài)內(nèi)存管理的函數(shù),學(xué)會(huì)了這些,對(duì)于以后數(shù)據(jù)結(jié)構(gòu)的內(nèi)容會(huì)更加得心應(yīng)手,所以希望大家能多多支持,接下來,下一章,我們跟大家講解一下,文件管理的內(nèi)容。學(xué)會(huì)了就可以更新通訊錄啦?。?!
以上就是使用c語言輕松實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管的詳細(xì)內(nèi)容,更多關(guān)于c語言實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 深入了解C語言的動(dòng)態(tài)內(nèi)存管理
- 詳解C語言中動(dòng)態(tài)內(nèi)存管理及柔性數(shù)組的使用
- C語言動(dòng)態(tài)內(nèi)存的分配最全面分析
- 一文帶你搞懂C語言動(dòng)態(tài)內(nèi)存管理
- 詳解C語言中的動(dòng)態(tài)內(nèi)存管理
- C語言動(dòng)態(tài)內(nèi)存分配圖文講解
- 一文帶你了解C語言中的動(dòng)態(tài)內(nèi)存管理函數(shù)
- C語言動(dòng)態(tài)內(nèi)存管理的原理及實(shí)現(xiàn)方法
- 詳解C語言中動(dòng)態(tài)內(nèi)存管理
- C語言中常見的六種動(dòng)態(tài)內(nèi)存錯(cuò)誤總結(jié)
- 一文解析C語言中動(dòng)態(tài)內(nèi)存管理
- C語言動(dòng)態(tài)內(nèi)存管理的實(shí)現(xiàn)示例
相關(guān)文章
C++實(shí)現(xiàn)鼠標(biāo)控制的黑框象棋
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)鼠標(biāo)控制的黑框象棋,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05C語言中數(shù)據(jù)結(jié)構(gòu)之鏈表歸并排序?qū)嵗a
這篇文章主要介紹了C語言中數(shù)據(jù)結(jié)構(gòu)之鏈表歸并排序?qū)嵗a的相關(guān)資料,需要的朋友可以參考下2017-05-05C語言數(shù)據(jù)結(jié)構(gòu)之中綴樹轉(zhuǎn)后綴樹的實(shí)例
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之中綴樹轉(zhuǎn)后綴樹的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-08-08