C語言動態(tài)內(nèi)存管理的實現(xiàn)示例
一、什么是動態(tài)內(nèi)存管理
動態(tài)內(nèi)存管理是一種內(nèi)存管理方法。它允許程序在運行時根據(jù)需要動態(tài)地申請和回收內(nèi)存空間。與靜態(tài)內(nèi)存管理(如變量和數(shù)組的預(yù)先分配)不同,動態(tài)內(nèi)存管理根據(jù)程序的需求即時分配內(nèi)存,并且分配的內(nèi)存大小就是程序要求的大小。
動態(tài)內(nèi)存分配的優(yōu)點:
- 能更好地適應(yīng)系統(tǒng)的動態(tài)需求,提高內(nèi)存的利用率
- 可以根據(jù)程序的實際需要分配內(nèi)存,而不是預(yù)先分配固定大小的內(nèi)存
- 允許程序在不需要時釋放內(nèi)存空間,供其他應(yīng)用程序使用
總結(jié)來說,動態(tài)內(nèi)存管理是一種靈活的內(nèi)存管理策略,它允許程序在運行時根據(jù)實際的需求分配和釋放內(nèi)存,從而提高內(nèi)存的使用效率和程序性能。
兩種開辟內(nèi)存空間的方式
我們現(xiàn)在已經(jīng)掌握的內(nèi)存開辟方式有兩種:一種是創(chuàng)建變量,還有一種就是創(chuàng)建數(shù)組。
int i = 5 //在??臻g上開辟4個字節(jié)char arr[10] //在??臻g上開辟10個字節(jié)的連續(xù)空間
但是上述的兩種開辟空間的方式有兩個特點:
- 空間開辟的大小是固定的。
- 數(shù)組在申明的時候,必須指定數(shù)組的長度,數(shù)組空間一旦確定了大小就不能再調(diào)整但是對于空間的需求,不僅僅是上述的情況。有時候我們需要的空間大小在程序運行的時候才能知道,那數(shù)組編譯時開辟空間的方式就不能滿足了。
在C語言引入了動態(tài)內(nèi)存開辟,讓程序員自己動態(tài)的申請和釋放空間,這樣就比較靈活了。
二、動態(tài)內(nèi)存管理的四個函數(shù)
要進行動態(tài)內(nèi)存分配,就要掌握四個重要的函數(shù):malloc、free、calloc、realloc。
1.malloc與free函數(shù)
C語言提供了一個動態(tài)內(nèi)存開辟的函數(shù):
void* malloc(size_t size);
這個函數(shù)會向內(nèi)存申請一塊連續(xù)可用的空間,并返回指向這塊空間的指針(地址)。
♥ 如果開辟成功,則返回一個指向開辟好空間的指針(這塊空間的起始地址)。
♥ 如果開辟失敗,則返回一個NULL指針,因此malloc的返回值一定要做檢查。
♥ 返回值的類型是void*,所以malloc函數(shù)并不知道開辟空間的類型,具體在使用的時候由使用者自己來決定。
♥ 如果參數(shù)size為0,那malloc的行為是標(biāo)準(zhǔn)未定義的,取決于編譯器。
上面malloc函數(shù)中的參數(shù)是size_t類型的,也就是一個無符號整型。而malloc的參數(shù)size就表示需要申請多少個字節(jié)的空間。我們都知道,void*類型的指針是無具體類型的指針,可以用來接收任意類型的地址,但是不能對其進行解引用。那malloc函數(shù)的返回值,我們要怎么接收呢?很簡單,對這個返回值進行強制類型轉(zhuǎn)換即可。因為我們在動態(tài)的申請空間時,心里就已經(jīng)知道自己需要多少個字節(jié)的空間,而且也知道申請的空間要用來存儲什么類型的數(shù)據(jù)。比如我們要申請20個字節(jié)的空間用來存放5個整型:
#include<stdio.h> #include<stdlib.h> int main() { //申請20個字節(jié)的空間用來存放5個整型 int* p = (int*)malloc(20); //對指針p進行檢查,看是否為NULL if (p == NULL) { perror("malloc"); return 1; } //使用空間 int i = 0; for (i = 0; i < 5; i++) { *(p + i) = i + 1; } //釋放空間 free(p); p = NULL; return 0; }
在申請空間時要考慮到,有申請失敗的時候,所以我們在動態(tài)的申請空間后,應(yīng)該要對malloc的返回值進行檢查。如果動態(tài)申請內(nèi)存失敗,那我們就不能再往下執(zhí)行程序。上面代碼中出現(xiàn)了另一個與malloc函數(shù)相匹配的free函數(shù),free函數(shù)是專門用來釋放動態(tài)申請的內(nèi)存空間的。free函數(shù)的定義如下:
void free(void* ptr)
free函數(shù)要傳進去的參數(shù)就是我們動態(tài)申請的內(nèi)存空間的起始地址,記住傳的參數(shù)一定得是這塊空間的起始地址。有借有還嘛!我們動態(tài)地向操作系統(tǒng)申請空間來使用,那使用完了就要把申請的內(nèi)存還回去。所以這里free函數(shù)就是??專門用來釋放和回收動態(tài)開辟的內(nèi)存空間的??。
但是對于free函數(shù)我們需要注意:
▲如果參數(shù)ptr指向的空間不是動態(tài)開辟的,那free函數(shù)的行為是未定義的
▲如果參數(shù)ptr是NULL指針,則函數(shù)什么事都不做
通過free函數(shù)釋放了動態(tài)開辟的空間以后,不是就完了!我們用來接收malloc函數(shù)的指針p,還指向著原來申請空間的起始地址,但是這塊空間已經(jīng)被釋放了。那以后要是不小又對指針p解引用來使用原來那塊空間,就會造成非法訪問,就會出現(xiàn)野指針。所以在釋放了動態(tài)開辟的內(nèi)存空間后,應(yīng)該將指針p置為空(NULL),這樣才是完整的。
malloc和free函數(shù)都聲明在stdlib.h的頭文件中,所以在使用這兩個函數(shù)之前,要記得包含頭文件stdlib.h。
由于動態(tài)開辟的內(nèi)存空間是連續(xù)的,那你把這塊空間看做數(shù)組也是可以的。因為數(shù)組在內(nèi)存中就是連續(xù)存放的。
2.calloc函數(shù)
C語言還提供了一個函數(shù)叫calloc,calloc函數(shù)也是用來動態(tài)內(nèi)存分配的。原型如下:
void* calloc(size_t num , size_t size)
♦ 函數(shù)的功能是為num個大小為size的元素開辟一塊空間,并且把空間的每個字節(jié)初始化為0
♦ 與函數(shù)malloc的區(qū)別只在于calloc函數(shù)會在返回地址之前把申請的空間的每個字節(jié)都初始化為0比如我們現(xiàn)要為5個大小為4個字節(jié)的整型元素申請一塊空間:
#include<stdio.h> #include<stdlib.h> int main() { //為5個大小為4字節(jié)的元素申請一塊空間 int* p = (int*)calloc(5,sizeof(int)); if (p == NULL) { perror("malloc"); return 1; } //打印空間內(nèi)容 int i = 0; for (i = 0; i < 5; i++) { printf("%d ", *(p + i)); } //釋放空間 free(p); p = NULL; return 0; }
程序運行結(jié)果:
calloc函數(shù)除了在傳參形式上與malloc有一點區(qū)別外,再一個就是它會將所申請空間的每個字節(jié)全部初始化為0,再返回此空間的起始地址。在上面的運行結(jié)果中,我們看到確實將此空間的每個字節(jié)初始化為了全0。我們也可以在內(nèi)存中查看此空間的內(nèi)容,如下圖,確實初始化了空間內(nèi)容為全0。
如果我們對申請的內(nèi)存空間的內(nèi)容要求初始化,那么可以很方便的使用calloc函數(shù)來完成任務(wù)。只是calloc函數(shù)在運行時間上可能就要比malloc函數(shù)慢,因為malloc函數(shù)不初始化申請的空間,所以兩個函數(shù)各有各的好處。同樣的,只要是動態(tài)開辟的內(nèi)存空間,都要判斷內(nèi)存空間是否開辟成功!都要用free函數(shù)釋放空間!都要把指針p置為空!養(yǎng)成良好的編程習(xí)慣,這有助于程序員少犯錯誤。
3.realloc函數(shù)
- realloc函數(shù)的出現(xiàn)讓動態(tài)內(nèi)存管理更加靈活。
- 有時會我們發(fā)現(xiàn)過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的使用內(nèi)存,我們一定會對內(nèi)存的大小做靈活的調(diào)整。那realloc函數(shù)就可以做到對動態(tài)開辟內(nèi)存大小進行調(diào)整。
realloc函數(shù)的定義形式如下:
void* realloc(void* ptr , size_t size)
? ptr是要調(diào)整的內(nèi)存地址
? size是調(diào)整之后的新大小
? 返回值為調(diào)整之后的內(nèi)存起始位置。
? 這個函數(shù)調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,還會將原來內(nèi)存中的數(shù)據(jù)移動到新的空間。
? realloc在調(diào)整內(nèi)存空間時存在三種情況:
◎ 情況1:原有空間之后有足夠大的空間。
◎ 情況2::原有空間之后沒有足夠大的空間
◎ 情況3:如果調(diào)整失敗了,則realloc會返回NULL(空指針)
對于上面的第四第五條,如果我們一開始用malloc申請了20個字節(jié)的空間,現(xiàn)在要調(diào)整為40個字節(jié)的空間,那就有上面的三種情況,看下面的代碼:
#include<stdio.h> #include<stdlib.h> int main() { //動態(tài)開辟20個字節(jié)的空間 int* ptr = (int*)malloc(5*sizeof(int)); if (ptr == NULL) { perror("malloc"); return 1; } //使用空間 int i = 0; for (i = 0; i < 5; i++) { *(ptr + i) = i + 1; } //調(diào)整原來空間的大小為40個字節(jié) int* p = (int*)realloc(ptr, 10 * sizeof(int)); if (p != NULL)//調(diào)整成功 { ptr = p; int i = 0; for (i = 5; i < 10; i++)//繼續(xù)使用空間 { *(ptr + i) = i + 1; } for (i = 0; i < 10; i++)//打印數(shù)據(jù) { printf("%d ", *(p + i)); } //釋放內(nèi)存 free(ptr); ptr = NULL; } else//調(diào)整失敗 { perror("realloc"); //釋放內(nèi)存 free(ptr); ptr = NULL; } return 0; }
程序運行結(jié)果:
如果使用realloc調(diào)整原來動態(tài)開辟的空間大小,那就有兩種情況:
- 一種是上面的情況1,如果原來申請的空間后面有足夠大的尚未使用的空間,那realloc就在原來申請空間的后面繼續(xù)開辟20個字節(jié)空間,realloc返回的還是原來空間的起始地址。
- 另一種是上面的情況2,如果原來開辟空間后面沒有足夠的未使用的空間,那realloc函數(shù)就會在堆區(qū)上重新找一塊連續(xù)的空間,這塊空間是滿足新的大小要求的,然后會將原來空間里的數(shù)據(jù)拷貝一份到新的空間里,然后再釋放原來那塊舊的空間,返回新的空間的起始地址。
- 最后一種就是情況3,realloc函數(shù)如果開辟空間失敗了,那realloc函數(shù)會返回空指針。在上面的情況2中我們知道,如果realloc函數(shù)在堆區(qū)上重新找一塊空間來滿足現(xiàn)在的大小要求,那realloc函數(shù)會釋放原來的空間,返回新空間的地址。那我們可不可以直接將指針p賦值給ptr,讓指針ptr來維護這塊新空間呢?答案是不可以的。我們要考慮到realloc函數(shù)會有申請空間失敗的時候,要是realloc申請空間失敗,我們直接將p賦值給ptr的話,那原來開辟的空間也就找不到了。所以在將指針p賦值給ptr時,應(yīng)該要提前判斷指針p是不是空指針,不是,再將指針p賦值給ptr。這樣才是正確的。
記住只要是動態(tài)開辟的空間,都要用free函數(shù)釋放申請的空間。
realloc還有一個特殊的用法,就是它可以完成和malloc一樣的功能,比如我們要用malloc申請20個字節(jié)的空間,那用realloc也可以實現(xiàn):
realloc(NULL , 20)
用realloc來開辟空間,只要記得傳的第一個參數(shù)是NULL(空指針),第二個參數(shù)就是我們要開辟的空間大小(單位字節(jié)),那現(xiàn)在的realloc就等價于malloc。
三、常見的動態(tài)內(nèi)存的錯誤
Ⅰ.對NULL指針的解引用操作
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(5 * sizeof(int)); *p = 10; free(p); p = NULL; return 0; }
這里就沒有對動態(tài)開辟的空間的指針(起始地址)進行檢查,如果malloc申請空間失敗,那malloc的返回值就是空指針。對NULL指針是不能對其進行解引用的。所以要對malloc的返回值做檢查:
Ⅱ.對動態(tài)開辟空間的越界訪問
對于動態(tài)申請的空間也是連續(xù)的,就像數(shù)組一樣。上面為10個整型動態(tài)的申請了40個字節(jié)的空間。但是在使用空間的時候,上面存在了越界訪問,我們申請來多少內(nèi)存空間,那我們就只有這塊空間的使用權(quán)限,不能越界使用空間。
Ⅲ.對非動態(tài)開辟內(nèi)存使用free釋放
#include<stdio.h> #include<stdlib.h> int main() { int a = 10; int* p = &a; free(p); return 0; }
我們說過只有動態(tài)開辟的空間,我們才會用free函數(shù)來釋放,對于其他非動態(tài)開辟的內(nèi)存空間,是不能用free來釋放的,這是未定義的。
Ⅳ.free只釋放了動態(tài)開辟內(nèi)存的一部分
當(dāng)我們用一個指針接收了動態(tài)開辟的內(nèi)存空間的起始地址時,則對指針使用自加操作符,會改變它的指向位置,不再指向動態(tài)申請空間的起始位置,這時再將這個指針傳給free函數(shù),那free釋放的就只是這塊空間的一部分,并沒有完全回收內(nèi)存。所以對于動態(tài)開辟空間的指針(起始地址),要注意不要對其有任何的改動。
Ⅴ.對同一塊動態(tài)內(nèi)存多次釋放
我們在寫代碼時,對于動態(tài)開辟的內(nèi)存,我們可能會重復(fù)的釋放,這也是不行的。像這種錯誤,可能在我們寫大量的代碼時會遇到,也就是你知道前面你動態(tài)開辟內(nèi)存了,但是由于寫了很多行的代碼,可能就會忘記自己已經(jīng)對動態(tài)開辟的內(nèi)存釋放過了,你又來一次釋放。這也是動態(tài)開辟內(nèi)存會遇到的錯誤。所以一定要檢查用了多少次動態(tài)內(nèi)存開辟,對應(yīng)的就要有多少次回收釋放。
Ⅵ.動態(tài)開辟內(nèi)存忘記釋放(內(nèi)存泄漏)
忘記釋放不再使用的動態(tài)開辟的空間會造成內(nèi)存泄漏,你是向操作系統(tǒng)申請空間來用,但是你用了卻沒有把這塊空間還給操作系統(tǒng),那這塊內(nèi)存就可能還存儲著一些值,這塊空間就用不了,一直占著內(nèi)存,造成內(nèi)存泄漏。當(dāng)然,如果你忘記了使用free函數(shù)釋放動態(tài)開辟的空間,在程序運行結(jié)束的時候,操作系統(tǒng)也會自動回收這塊內(nèi)存。所以本質(zhì)上動態(tài)開辟的空間是不會丟的,但是如果程序一直運行著,那就可能會造成內(nèi)存泄漏。
切記:動態(tài)開辟的空間一定要釋放,并且正確釋放
到此這篇關(guān)于C語言動態(tài)內(nèi)存管理的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)C語言動態(tài)內(nèi)存管理內(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)存管理函數(shù)
- C語言動態(tài)內(nèi)存管理的原理及實現(xiàn)方法
- 詳解C語言中動態(tài)內(nèi)存管理
- C語言中常見的六種動態(tài)內(nèi)存錯誤總結(jié)
- 一文解析C語言中動態(tài)內(nèi)存管理
相關(guān)文章
C語言數(shù)據(jù)結(jié)構(gòu)圖的創(chuàng)建與遍歷實驗示例
這篇文章主要為大家介紹了C語言數(shù)據(jù)結(jié)構(gòu)圖的創(chuàng)建與遍歷實驗示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06解析C++編程中如何使用設(shè)計模式中的狀態(tài)模式結(jié)構(gòu)
這篇文章主要介紹了如何在C++編程中適用設(shè)計模式中的狀態(tài)模式結(jié)構(gòu),狀態(tài)模式強調(diào)將特定狀態(tài)相關(guān)的邏輯分散到一些類的狀態(tài)類中,需要的朋友可以參考下2016-03-03深入分析C++中兩個大數(shù)相乘結(jié)果不正確的問題
本篇文章是對C++中兩個大數(shù)相乘結(jié)果不正確的問題進行了詳細的分析介紹,需要的朋友參考下2013-05-05