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