一文帶你了解C語言中的動(dòng)態(tài)內(nèi)存管理函數(shù)
1.什么是動(dòng)態(tài)內(nèi)存管理
平時(shí)我們寫代碼,一種非常常見的寫法是:
int a = 0; // 創(chuàng)建一個(gè)變量 int arr[10] = {0}; // 創(chuàng)建一個(gè)數(shù)組
當(dāng)你創(chuàng)建變量a的時(shí)候,其實(shí)是向內(nèi)存中申請(qǐng)了4個(gè)字節(jié)的空間來存放一個(gè)整數(shù)。而當(dāng)你創(chuàng)建數(shù)組arr的時(shí)候,是向內(nèi)存中申請(qǐng)了40個(gè)字節(jié)的空間來存放10個(gè)整數(shù)。當(dāng)你這么寫了之后,從a和arr創(chuàng)建開始,一直到程序結(jié)束,它們都只會(huì)占用4個(gè)和40個(gè)字節(jié)的空間,不會(huì)多也不會(huì)少!換句話說,它們的大小并不會(huì)變化,是靜態(tài)的。
與之相反,如果你向內(nèi)存中申請(qǐng)一塊空間,這塊空間可以一會(huì)大,一會(huì)小,你覺得不夠了就擴(kuò)容,你覺得空間太多了就縮容,你想要多少空間就來多少空間,這塊空間的大小是會(huì)變化的,所以我們認(rèn)為它是動(dòng)態(tài)的。對(duì)這樣可以改變大小的內(nèi)存空間的管理,稱之為動(dòng)態(tài)內(nèi)存管理。
你可能會(huì)想:這么神奇?我還能自己操縱向內(nèi)存申請(qǐng)的空間大?。窟@是怎么做到的呢?別急,聽我慢慢道來。
當(dāng)你創(chuàng)建一個(gè)局部變量時(shí),比如int a = 0;,變量a是存儲(chǔ)在棧區(qū)上的。一般來說,如果你想要改變棧區(qū)上申請(qǐng)的空間的大小,是非常困難的,因?yàn)檫@塊空間是編譯器提前幫你算好的,一旦進(jìn)入到函數(shù)內(nèi)部就自動(dòng)幫你開辟好了,我們作為程序員并沒有太多的操作空間。但是內(nèi)存中有一塊空間,專門用來給我們做動(dòng)態(tài)內(nèi)存管理,這塊空間就是堆區(qū)。如果我們?cè)诙褏^(qū)上申請(qǐng)空間,就可以做到想要多少空間就來多少空間,不夠了還可以繼續(xù)申請(qǐng),非常靈活。
2.為什么要有動(dòng)態(tài)內(nèi)存管理
靜態(tài)的內(nèi)存管理,某些情況下會(huì)顯得很死板。比如,我要實(shí)現(xiàn)一個(gè)通訊錄,是不是要?jiǎng)?chuàng)建一個(gè)數(shù)組來存儲(chǔ)聯(lián)系人的信息?那好,我要開辟多大的空間?如果容量是100人,假設(shè)我要存儲(chǔ)200個(gè)人的信息呢?你可能會(huì)說,那好,給我1w個(gè)空間!那我如果只存3個(gè)人的信息呢?這么多的空間是不是就浪費(fèi)了?
所以,靜態(tài)的內(nèi)存管理有2個(gè)硬傷:
- 開辟空間太少,不夠用。
- 開辟空間太多,造成空間浪費(fèi)。
而這2個(gè)問題可以用動(dòng)態(tài)內(nèi)存管理的方式完美解決??臻g太少,咱再多來點(diǎn)!空間太多,我還可以縮容,減少空間消耗。
3.如何進(jìn)行動(dòng)態(tài)內(nèi)存管理
這可能是大家最關(guān)心的問題。我應(yīng)該怎么樣進(jìn)行動(dòng)態(tài)內(nèi)存管理呢?C語言提供了4個(gè)函數(shù),專門用來進(jìn)行動(dòng)態(tài)內(nèi)存管理。這四個(gè)函數(shù)分別是malloc, free, calloc, realloc。使用這幾個(gè)函數(shù)都需要引用頭文件stdlib.h。
接下來,我將詳細(xì)介紹這幾個(gè)函數(shù)的用法。
3.1 malloc
如果你想要40個(gè)字節(jié)的空間來存儲(chǔ)10個(gè)int,你可以跟內(nèi)存說:“喂,內(nèi)存!給我40個(gè)字節(jié)的空間!我要用!”也可以直接使用malloc函數(shù)。malloc函數(shù)的聲明如下:
void* malloc(size_t size);
其實(shí)你只需要給malloc傳你需要的空間大小就行了。比如你想申請(qǐng)40個(gè)字節(jié),就直接malloc(40);,此時(shí)malloc就會(huì)屁顛屁顛的跑去內(nèi)存那里,申請(qǐng)40個(gè)字節(jié)的空間,然后返回這塊空間的起始地址。有了前面“什么是動(dòng)態(tài)內(nèi)存管理”的講解,你應(yīng)該很清楚,這40個(gè)字節(jié)的空間是在內(nèi)存的堆區(qū)上的。
注意,malloc返回空間的地址,類型是void*。啥是void*呢?其實(shí),void*的意思是,malloc不知道返回的地址是什么類型的,這要你來告訴他。比如,你想用這40個(gè)字節(jié)的空間來存儲(chǔ)10個(gè)int,你當(dāng)然希望這個(gè)指針是int*類型的,此時(shí)你就會(huì)用一個(gè)int*類型的指針來接收。由于void*和int*畢竟是兩種不同的類型,所以需要強(qiáng)制類型轉(zhuǎn)換。具體的寫法如下:
int* ptr = (int*)malloc(40);
此時(shí)你就有了一個(gè)指針ptr,指向了動(dòng)態(tài)開辟的40個(gè)字節(jié)的連續(xù)空間了,你就可以在這塊空間里為所欲為了。接下來,你還需要掌握一些細(xì)節(jié)。
malloc函數(shù)申請(qǐng)空間一定會(huì)成功嗎?那可不見得!如果你一次申請(qǐng)的空間太多了,比如malloc(INT_MAX);,INT_MAX是整形變量能夠存儲(chǔ)的最大值,你一次申請(qǐng)這么多字節(jié)的空間,那很有可能失敗呀!或者還有一種可能,你的電腦已經(jīng)運(yùn)行了幾百個(gè)進(jìn)程了,內(nèi)存已經(jīng)快被耗干了,哪怕你申請(qǐng)的空間不是很多,也有可能申請(qǐng)失敗。
malloc函數(shù)在申請(qǐng)空間失敗的時(shí)候,會(huì)返回NULL指針。每次使用malloc函數(shù)的時(shí)候,都必須要檢驗(yàn)返回值是不是NULL,否則后面在使用這個(gè)指針的時(shí)候,有可能會(huì)有對(duì)NULL指針的解引用操作,這是非常危險(xiǎn)的!檢查的示例代碼如下:
if (ptr == NULL) { // 錯(cuò)誤處理 perror("malloc()"); exit(-1); }
錯(cuò)誤處理的代碼應(yīng)該根據(jù)實(shí)際情況來寫,我這里只是給了一個(gè)常見的處理方式:用perror函數(shù)報(bào)個(gè)錯(cuò),接著直接用exit函數(shù)結(jié)束進(jìn)程,非常簡(jiǎn)單粗暴。當(dāng)你驗(yàn)完貨,發(fā)現(xiàn)貨的質(zhì)量不對(duì),直接扭頭就走,不玩了,exit掉。而當(dāng)你覺得這貨的質(zhì)量還可以,就可以使用了。
使用起來非常簡(jiǎn)單,ptr指向了這塊空間的起始地址,由于類型是int*,+1就跳過一個(gè)int,+2就跳過2個(gè)int,以此類推。每次解引用,就可以訪問這塊空間了,比如把1~10放進(jìn)去:
for (int i = 0; i < 10; i++) { *(ptr + i) = i + 1; }
由于在C語言中,*(a+b)就等價(jià)于a[b],所以上面的代碼也可以這么寫:
for (int i = 0; i < 10; i++) { ptr[i] = i + 1; }
有沒有發(fā)現(xiàn),ptr就像一個(gè)數(shù)組一樣,這個(gè)數(shù)組的容量是10個(gè)int。
3.2 free
注意,動(dòng)態(tài)內(nèi)存管理的空間需要程序員手動(dòng)釋放!前面我們用malloc開辟了一塊空間,并把這塊空間的起始地址交給ptr指針來看管,最后,我們還要使用free函數(shù)來釋放這塊空間。使用方式非常簡(jiǎn)單,你想釋放誰就free誰,比如:
free(ptr);
傳給free函數(shù)的指針必須指向一塊動(dòng)態(tài)開辟空間的起始位置!言外之意就是,你malloc出來一塊空間交給ptr管理后,你還能修改ptr嗎?答案是:不行。你一旦把ptr給改了,就找不到這塊空間的起始位置了,就沒辦法把它free掉了。就相當(dāng)于,警察局有一個(gè)臥底,這個(gè)臥底和一個(gè)領(lǐng)導(dǎo)是單線聯(lián)系的,如果領(lǐng)導(dǎo)被干掉了,就沒有人知道這個(gè)臥底的身份了。ptr就是這個(gè)領(lǐng)導(dǎo),malloc開辟出來的這塊空間就是這個(gè)臥底。
你可能會(huì)想:如果我不free,會(huì)怎么樣呢?事實(shí)上,如果程序員沒有手動(dòng)回收動(dòng)態(tài)申請(qǐng)的空間,當(dāng)程序結(jié)束時(shí),這塊空間會(huì)自動(dòng)還給操作系統(tǒng);如果程序一直不結(jié)束,這塊空間就一直不會(huì)被回收,就一直擱那,占據(jù)資源,浪費(fèi)內(nèi)存,此時(shí)我們稱,造成了內(nèi)存泄漏。
free函數(shù)會(huì)把ptr指針置成NULL嗎?答案是:不會(huì)。free函數(shù)沒有這個(gè)能力。如果你函數(shù)這個(gè)章節(jié)學(xué)的不錯(cuò),你應(yīng)該知道,C語言調(diào)用函數(shù)時(shí)是值傳遞,函數(shù)內(nèi)部的形參是實(shí)參的一份臨時(shí)拷貝,改變形參不會(huì)影響實(shí)參。也就是說,free函數(shù)內(nèi)部會(huì)有另一個(gè)指針拷貝ptr的值,free函數(shù)會(huì)把這個(gè)指針指向的內(nèi)存空間還給操作系統(tǒng),但是沒有能力影響外面的ptr指針。
既然ptr指針沒有被置成NULL,也就是說,ptr的還是指向原來malloc申請(qǐng)的那塊空間,但是這塊空間已經(jīng)還給操作系統(tǒng)了!如果還使用ptr指針訪問這塊空間,就造成了內(nèi)存的非法訪問,因?yàn)榇藭r(shí)ptr是一個(gè)野指針!這是很危險(xiǎn)的一件事。所以,在free掉這個(gè)指針之后,最好把它置為NULL,就像這樣:
free(ptr); ptr = NULL;
最后還有一個(gè)細(xì)節(jié),如果傳給free函數(shù)的是NULL指針,free函數(shù)什么也不做。free(NULL);相當(dāng)于什么也沒發(fā)生。
來看看一段完整的代碼,來演示malloc和free:
#include <stdio.h> #include <stdlib.h> int main() { // 開辟空間 int* ptr = (int*)malloc(10 * sizeof(int)); // 檢驗(yàn)是否開辟成功 if (NULL == ptr) { perror("malloc"); return 1; } // 把1~10放到這塊空間里 for (int i = 0; i < 10; i++) { ptr[i] = i + 1; } // 打印這塊空間里的值 for (int i = 0; i < 10; i++) { printf("%d ", ptr[i]); } // 釋放這塊空間,別忘了把ptr置NULL free(ptr); ptr = NULL; return 0; }
輸出結(jié)果:
3.3 calloc
calloc函數(shù)的使用和malloc函數(shù)幾乎完全一樣,只有2個(gè)微小的區(qū)別:
- calloc函數(shù)有2個(gè)參數(shù),分別表示開辟空間要存儲(chǔ)的元素個(gè)數(shù)和每個(gè)元素的大小。malloc函數(shù)只有一個(gè)參數(shù),表示要開辟的空間的總大小。
- calloc函數(shù)會(huì)把開辟的空間初始化成0。malloc函數(shù)并不會(huì)做任何處理,所以空間內(nèi)存儲(chǔ)的是隨機(jī)值。也就是說,calloc由于會(huì)對(duì)開辟的空間進(jìn)行初始化,效率會(huì)低一點(diǎn),相當(dāng)于malloc+memset。
由于使用上就這點(diǎn)區(qū)別,大家看個(gè)例子就懂了。我只是把上面的代碼改一下:
#include <stdio.h> #include <stdlib.h> int main() { // 開辟空間(40個(gè)字節(jié),即10個(gè)int) int* ptr = (int*)calloc(10, sizeof(int)); // 檢驗(yàn)是否開辟成功 if (NULL == ptr) { perror("calloc"); return 1; } // 打印這塊空間里的值 for (int i = 0; i < 10; i++) { printf("%d ", ptr[i]); } // 釋放這塊空間,別忘了把ptr置NULL free(ptr); ptr = NULL; return 0; }
輸出結(jié)果:
輸出結(jié)果側(cè)面驗(yàn)證了,calloc會(huì)把開辟的空間全部初始化成0。
3.4 realloc
剛剛講了3個(gè)函數(shù)了,也只是把空間開辟出來,然后再還給操作系統(tǒng),似乎也沒有什么新鮮的?其實(shí),最后一個(gè)函數(shù),realloc,會(huì)讓你大開眼界。
realloc函數(shù)可以幫你改變開辟的空間的大小,你可以讓這塊空間任意的變大變小,非常靈活!
realloc函數(shù)的聲明如下:
void* realloc(void* ptr, size_t new_size);
第一個(gè)參數(shù)是你想擴(kuò)容的空間的起始地址,也就是一開始接收malloc和calloc返回值的指針,第二個(gè)參數(shù)是你想把空間改變的新大小。注意:不是相對(duì)大?。∈切麓笮?!
比如,一開始先malloc出40個(gè)字節(jié)的空間,這塊空間能存10個(gè)int:
int* ptr = (int*)malloc(40); if (NULL == ptr) { // 錯(cuò)誤處理 // ... }
如果這塊空間存滿了,你還想再存10個(gè)int進(jìn)去,此時(shí)新空間的大小就是20個(gè)int,即80個(gè)字節(jié),比原空間多出40個(gè)字節(jié)。再次強(qiáng)調(diào):realloc的第二個(gè)參數(shù)是新空間的總大小!是最終的大小,而不是相對(duì)的大小。所以如果把原來的40個(gè)字節(jié)的空間翻一倍,應(yīng)該寫realloc(ptr, 80);而不是realloc(ptr, 40);,后一種寫法相當(dāng)于空間大小沒有改變。
realloc函數(shù)會(huì)返回新空間的起始地址,如果擴(kuò)容失敗就會(huì)返回NULL。那能不能這么寫?
ptr = (int*)realloc(ptr, 80);
答案是:不行!你想想,如果擴(kuò)容失敗,返回NULL給ptr,相當(dāng)于既沒有擴(kuò)容成功,還把原來的空間的地址給弄丟了,那不是賠了夫人又折兵嗎?所以,還是那句話,得先驗(yàn)貨,再使用。比如:
int* tmp = (int*)realloc(ptr, 80); if (tmp == NULL) { // 擴(kuò)容失敗 perror("realloc"); exit(-1); } else { // 擴(kuò)容成功 ptr = tmp; }
那realloc函數(shù)具體是如何做到改變動(dòng)態(tài)開辟空間的大小的呢?
realloc函數(shù)會(huì)先看一眼,原來空間后面的空間夠不夠。比如上面的例子中,realloc函數(shù)會(huì)去看,原來的空間再往后數(shù)40個(gè)字節(jié)的空間有沒有被占用,如果沒有,realloc會(huì)直接把原來空間的后40個(gè)字節(jié)的空間給申請(qǐng)到,再把原來空間的起始地址給返回,此時(shí)是原地?cái)U(kuò)容。
但是如果原來的空間的后面的空間被占用了呢?那就得另外找一塊80個(gè)字節(jié)的空間,把原來的40個(gè)字節(jié)的數(shù)據(jù)拷貝過去,接著釋放掉原來的空間,返回新空間的起始地址。這種擴(kuò)容方式稱為異地?cái)U(kuò)容。
還有一點(diǎn),realloc函數(shù)也可以像malloc函數(shù)一樣使用。當(dāng)?shù)谝粋€(gè)參數(shù)是NULL時(shí),realloc函數(shù)的表現(xiàn)和malloc一樣。也就是說,malloc(40);和realloc(NULL, 40);等價(jià)。
我們把最初的程序改造一下,如下:
#include <stdio.h> #include <stdlib.h> int main() { // 開辟空間 int* ptr = (int*)malloc(10 * sizeof(int)); // 檢驗(yàn)是否開辟成功 if (NULL == ptr) { perror("malloc"); return 1; } // 把1~10放到這塊空間里 for (int i = 0; i < 10; i++) { ptr[i] = i + 1; } // 打印這塊空間里的值 for (int i = 0; i < 10; i++) { printf("%d ", ptr[i]); } printf("\n"); // 擴(kuò)容 int* tmp = (int*)realloc(ptr, 20 * sizeof(int)); if (NULL == tmp) { // 擴(kuò)容失敗 perror("realloc"); return 1; } else { // 擴(kuò)容成功 ptr = tmp; } // 把11~20也放進(jìn)去 for (int i = 10; i < 20; i++) { ptr[i] = i + 1; } // 打印這塊空間的所有值 for (int i = 0; i < 20; i++) { printf("%d ", ptr[i]); } // 釋放這塊空間,別忘了把ptr置NULL free(ptr); ptr = NULL; return 0; }
輸出結(jié)果如下:
總結(jié)
1.有時(shí)我們需要進(jìn)行動(dòng)態(tài)的內(nèi)存管理,如果是靜態(tài)內(nèi)存管理,空間給少了不夠用,給多了又浪費(fèi)。動(dòng)態(tài)內(nèi)存管理是在堆區(qū)上進(jìn)行的。
2.動(dòng)態(tài)內(nèi)存管理需要我們掌握4個(gè)函數(shù),分別是malloc, free, calloc和realloc,它們分別扮演著重要的角色。
3.malloc和calloc負(fù)責(zé)開辟空間,并返回空間的起始地址。如果開辟空間失敗會(huì)返回NULL。
4.malloc函數(shù)只有一個(gè)參數(shù),表示開辟空間的總大小;calloc函數(shù)有兩個(gè)參數(shù),分別表示開辟空間能存儲(chǔ)的元素個(gè)數(shù)和每個(gè)元素的大小。
5.malloc函數(shù)不會(huì)對(duì)空間進(jìn)行處理,calloc函數(shù)會(huì)把空間初始化成全0。
6.free函數(shù)可以釋放一塊動(dòng)態(tài)內(nèi)存,傳參時(shí)必須傳這塊空間的起始地址。釋放完后最好置NULL,防止出現(xiàn)野指針。free(NULL)這種寫法free函數(shù)什么都不會(huì)做。
7.realloc函數(shù)可以動(dòng)態(tài)調(diào)整開辟空間的大小,傳遞參數(shù)時(shí),第一個(gè)參數(shù)是動(dòng)態(tài)內(nèi)存的起始地址,第二個(gè)參數(shù)是新空間的總大小。如果第一個(gè)參數(shù)是NULL,realloc的表現(xiàn)和malloc一樣。
8.使用malloc, calloc, realloc都需要判斷返回值是不是NULL,并對(duì)相應(yīng)情況進(jìn)行處理。
到此這篇關(guān)于一文帶你了解C語言中的動(dòng)態(tài)內(nèi)存管理函數(shù)的文章就介紹到這了,更多相關(guān)C語言動(dòng)態(tài)內(nèi)存管理函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(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語言輕松實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管
- 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)文章
深入學(xué)習(xí)C語言mmap和shm*的使用方法技巧
本文將詳細(xì)介紹mmap和shm的工作原理,包括它們?cè)趦?nèi)存映射和共享內(nèi)存方面的優(yōu)勢(shì)和適用場(chǎng)景,同時(shí),文章還會(huì)分享一些使用mmap和shm的技巧和經(jīng)驗(yàn),以幫助讀者優(yōu)化并提高程序性能,使你能夠在實(shí)際項(xiàng)目中更好地利用這些技術(shù)來加速數(shù)據(jù)共享和多線程應(yīng)用2023-10-10C++線性表深度解析之動(dòng)態(tài)數(shù)組與單鏈表和棧及隊(duì)列的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)動(dòng)態(tài)數(shù)組、單鏈表、棧、隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05C++實(shí)現(xiàn)LeetCode(100.判斷相同樹)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(100.判斷相同樹),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07基于Matlab圖像處理的公路裂縫檢測(cè)實(shí)現(xiàn)
隨著公路的大量投運(yùn),公路日常養(yǎng)護(hù)和管理已經(jīng)成為制約公路運(yùn)營(yíng)水平提高的瓶頸,特別是路面狀態(tài)采集、檢測(cè)維護(hù)等工作更是對(duì)傳統(tǒng)的公路運(yùn)維模式提出了挑戰(zhàn)。這篇文章主要介紹了如何通過Matlab圖像處理實(shí)現(xiàn)公路裂縫檢測(cè),感興趣的可以了解一下2022-02-02