C語(yǔ)言動(dòng)態(tài)內(nèi)存管理深入探討
1.動(dòng)態(tài)內(nèi)存開(kāi)辟的原因
常見(jiàn)的內(nèi)存開(kāi)辟方式
int val = 20;//在??臻g上開(kāi)辟四個(gè)字節(jié)的空間
char arr[10] = {0};//在??臻g上開(kāi)辟10個(gè)字節(jié)的連續(xù)空間
上面開(kāi)辟空間的方式有兩個(gè)特點(diǎn):
1.空間開(kāi)辟的大小是固定的;
2. 數(shù)組在聲明的時(shí)候,必須指定數(shù)組的長(zhǎng)度,它所需要的空間在編譯時(shí)分配;
但是對(duì)于空間的需求,不只是上面的情況,有時(shí)候需要的空間大小在程序運(yùn)行的時(shí)候才能得知,這時(shí)候數(shù)組的編譯時(shí)開(kāi)辟空間的方式就不能滿足了。
所以需要?jiǎng)討B(tài)開(kāi)辟內(nèi)存
2.動(dòng)態(tài)內(nèi)存函數(shù)的介紹
動(dòng)態(tài)內(nèi)存的開(kāi)辟都是在內(nèi)存的堆區(qū)中進(jìn)行開(kāi)辟的
2.1malloc和free
C語(yǔ)言提供了一個(gè)動(dòng)態(tài)開(kāi)辟內(nèi)存的函數(shù):
void* malloc(size_t size);
malloc函數(shù)向內(nèi)存申請(qǐng)一塊連續(xù)可用的空間,并返回指向這塊空間起始位置的指針。
1.如果開(kāi)辟成功,則返回一個(gè)指向開(kāi)辟好的空間的指針;
2.如果開(kāi)辟失敗,則返回一個(gè)NULL指針,因此malloc的返回值一定要做檢查,不然可能會(huì)造成野指針的問(wèn)題;
3.返回值的類型時(shí)void*,所以malloc函數(shù)并不知道開(kāi)辟空間的類型,具體在使用的時(shí)候由使用者自己來(lái)決定返回值的類型;
4.如果參數(shù)size為0,此時(shí)malloc函數(shù)的行為是標(biāo)準(zhǔn)未定義的,取決于程序運(yùn)行時(shí)使用的編譯器;
malloc的使用:
vint main()
{
int* p = (int*)malloc(40);//向內(nèi)存申請(qǐng)一塊40字節(jié)的空間,并對(duì)放回的指針類型轉(zhuǎn)換
if (p == NULL)//返回NULL指針時(shí)讓打印錯(cuò)誤信息并讓程序結(jié)束
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
return 0;
}要記得包含頭文件<stdlib.h>
這樣就對(duì)開(kāi)辟的內(nèi)存空間進(jìn)行了一個(gè)使用了,但是還有點(diǎn)問(wèn)題,因?yàn)橄騼?nèi)存申請(qǐng)的空間沒(méi)有進(jìn)行釋放。
所以這時(shí)就引入了另一個(gè)函數(shù)free
C語(yǔ)言提供了另一個(gè)函數(shù)free,專門(mén)用來(lái)做動(dòng)態(tài)內(nèi)存的釋放和回收的
void free(void* ptr);
free函數(shù)用來(lái)釋放動(dòng)態(tài)開(kāi)辟的內(nèi)存
1.如果參數(shù)ptr指向的空間是不是動(dòng)態(tài)開(kāi)辟的,那么free函數(shù)的行為是未定義的;
2.如果參數(shù)ptr是NULL指針,則free函數(shù)什么事也不做;
malloc和free函數(shù)都聲明在stdlib.h頭文件中
free的使用:
int main()
{
int* p = (int*)malloc(40);//向內(nèi)存申請(qǐng)一塊40字節(jié)的空間,并對(duì)放回的指針類型轉(zhuǎn)換
if (p == NULL)//返回NULL指針時(shí)讓打印錯(cuò)誤信息并讓程序結(jié)束
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
free(p);//釋放p所指向的動(dòng)態(tài)內(nèi)存
p == NULL;//將p的值置為NULL
return 0;
}free的參數(shù)一定是動(dòng)態(tài)開(kāi)辟內(nèi)存空間的那個(gè)起始位置的地址,否則會(huì)報(bào)錯(cuò)
在用free釋放完動(dòng)態(tài)開(kāi)辟的內(nèi)存之后,要對(duì)之前指向動(dòng)態(tài)開(kāi)辟空間的那個(gè)指針置為NULL,因?yàn)槟菈K動(dòng)態(tài)開(kāi)辟的空間已經(jīng)被操作系統(tǒng)回收了,沒(méi)有了訪問(wèn)的權(quán)限,所以要讓p的值為NULL,避免野指針的問(wèn)題。
如果對(duì)動(dòng)態(tài)內(nèi)存開(kāi)辟的空間沒(méi)有釋放掉,會(huì)出現(xiàn)一個(gè)內(nèi)存泄漏的問(wèn)題。
2.2calloc
C語(yǔ)言還提供了一個(gè)calloc函數(shù),calloc也是用來(lái)進(jìn)行動(dòng)態(tài)內(nèi)存的分配
void* calloc(size_t num, size_t size);
1.calloc的功能是為num個(gè)字節(jié)大小為size的元素開(kāi)辟一個(gè)空間,并且把空間的每個(gè)字節(jié)的數(shù)據(jù)都初始化為0,然后返回這塊連續(xù)空間的起始位置的地址;
2.與malloc函數(shù)的區(qū)別只在于,calloc在返回地址之前會(huì)把申請(qǐng)的空間的每個(gè)字節(jié)的數(shù)據(jù)都初始化為全0;
calloc的使用:
int main()
{
int* p = (int*)calloc(10, 4);
if (p == NULL)
{
perror("calloc");
return 1;
}
free(p);
p = NULL;
return 0;
}內(nèi)存情況:

可以看到,動(dòng)態(tài)開(kāi)辟的40個(gè)字節(jié)的空間都被初始化為全0
所以如果要對(duì)動(dòng)態(tài)開(kāi)辟的空間進(jìn)行初始化,可以直接使用calloc函數(shù)來(lái)完成
2.3realloc
有時(shí)會(huì)發(fā)現(xiàn)申請(qǐng)的空間太大或者太小了,為了合理的使用內(nèi)存,一定會(huì)對(duì)內(nèi)存的大小做一個(gè)靈活的調(diào)整,那么realloc函數(shù)就可以做到對(duì)動(dòng)態(tài)開(kāi)辟內(nèi)存大小的調(diào)整
realloc函數(shù)的出現(xiàn)讓動(dòng)態(tài)內(nèi)存管理更加靈活
void* realloc (void* ptr, size_t size);
1.ptr是要調(diào)整的內(nèi)存空間;
2.size是調(diào)整之后的新大小;
3.返回值為調(diào)整之后的內(nèi)存起始位置;
4.realloc函數(shù)在調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,還會(huì)將原來(lái)內(nèi)存中的數(shù)據(jù)移動(dòng)到新的空間;
realloc函數(shù)在調(diào)整內(nèi)存空間時(shí)存在兩種情況:
情況1:要調(diào)整的空間之后有足夠的空間來(lái)存放調(diào)整之后的大小

情況2:要調(diào)整的空間之后沒(méi)有足夠大的空間

如果是情況1,那么就在原有的內(nèi)存之后追加新的空間,原來(lái)空間的數(shù)據(jù)不發(fā)生變化。
如果是情況2,原有空間之后沒(méi)有足夠多的空間,此時(shí)就會(huì)在堆空間上另找一個(gè)合適大小的連續(xù)空間來(lái)使用,這樣函數(shù)返回的是一個(gè)新的內(nèi)存地址

并且realloc函數(shù)還會(huì)將原空間的數(shù)據(jù)移動(dòng)到新的空間。
如果realloc函數(shù)在堆區(qū)中都找不到一塊合適的空間,則會(huì)返回NULL指針。
realloc的使用:
int main()
{
int* p = (int*)calloc(10, 4);
if (p == NULL)
{
perror("calloc");
return 1;
}
p = realloc(p, 1000);
if (p == NULL)
{
perror("realloc");
return 1;
}
free(p);
p = NULL;
return 0;
}其次,realloc函數(shù)還能使原有空間變?。?/p>
使用:
int main()
{
int* p = (int*)calloc(10, 4);
if (p == NULL)
{
perror("calloc");
return 1;
}
p = realloc(p, 20);
if (p == NULL)
{
perror("realloc");
return 1;
}
free(p);
p = NULL;
return 0;
}內(nèi)存情況:

3.常見(jiàn)的動(dòng)態(tài)內(nèi)存錯(cuò)誤
3.1對(duì)NULL指針的解引用操作

這里編譯器直接把對(duì)NULL指針的解引用操作給取消掉了,如果在其他的編譯器下運(yùn)行,可能會(huì)出現(xiàn)問(wèn)題,所以一定要對(duì)動(dòng)態(tài)開(kāi)辟內(nèi)存函數(shù)的返回值進(jìn)行一個(gè)NULL指針的判斷。
3.2對(duì)動(dòng)態(tài)開(kāi)辟空間的越界訪問(wèn)
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i <= 10; i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}其中*(p + 10) = 10;時(shí)對(duì)動(dòng)態(tài)開(kāi)辟的空間進(jìn)行了一個(gè)越界訪問(wèn)了,編譯器直接報(bào)錯(cuò)

3.3對(duì)非動(dòng)態(tài)開(kāi)辟內(nèi)存使用free
對(duì)棧區(qū)上的空間使用free:
int main()
{
int a = 0;
free(&a);
return 0;
}此時(shí)編譯器也會(huì)給出一個(gè)錯(cuò)誤

3.4使用釋放一塊動(dòng)態(tài)開(kāi)辟內(nèi)存的一部分
int main()
{
int* p = (int*)malloc(40);
p++;
free(p);
return 0;
}此時(shí)p沒(méi)有指向動(dòng)態(tài)開(kāi)辟內(nèi)存的起始位置
編譯器同樣給出了一個(gè)錯(cuò)誤

3.5對(duì)同一塊動(dòng)態(tài)內(nèi)存多次釋放
int main()
{
int* p = (int*)malloc(40);
free(p);
free(p);
return 0;
}p已經(jīng)釋放過(guò)了

3.6動(dòng)態(tài)開(kāi)辟內(nèi)存忘記釋放(內(nèi)存泄漏)
在向內(nèi)存申請(qǐng)了一塊空間之后沒(méi)有對(duì)其進(jìn)行釋放會(huì)造成內(nèi)存泄漏的問(wèn)題
會(huì)迅速吃滿你的內(nèi)存
int main()
{
while (1)
{
malloc(40);
}
return 0;
}如圖:

如果程序在沒(méi)有結(jié)束之前申請(qǐng)的內(nèi)存都沒(méi)有進(jìn)行釋放的話,就會(huì)出現(xiàn)內(nèi)存泄漏的問(wèn)題。所以在申請(qǐng)好一塊內(nèi)存之后要記得對(duì)其進(jìn)行釋放。
總結(jié):
忘記釋放不再使用的動(dòng)態(tài)內(nèi)存開(kāi)辟的空間就會(huì)造成內(nèi)存泄漏的問(wèn)題,而且動(dòng)態(tài)開(kāi)辟的空間要正確釋放。
4.練習(xí)
4.1練習(xí)1
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}請(qǐng)問(wèn)運(yùn)行Test 函數(shù)會(huì)有什么樣的結(jié)果?
先創(chuàng)建了一個(gè)字符指針變量賦值為NULL,然后調(diào)用函數(shù)GerMemory,調(diào)用函數(shù)時(shí)形參只是一份對(duì)實(shí)參的臨時(shí)拷貝,函數(shù)調(diào)用時(shí),申請(qǐng)了一塊動(dòng)態(tài)開(kāi)辟內(nèi)存,但是并沒(méi)有返回p,p在函數(shù)調(diào)用結(jié)束后銷毀了,所以此時(shí)str指向的還是NULL,strcpy使用時(shí)對(duì)NULL指針進(jìn)行了解引用,造成了非法訪問(wèn),野指針的問(wèn)題,也造成了動(dòng)態(tài)內(nèi)存錯(cuò)誤
4.1練習(xí)2
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}請(qǐng)問(wèn)運(yùn)行Test 函數(shù)會(huì)有什么樣的結(jié)果?
調(diào)用GetMemory函數(shù)時(shí)在棧區(qū)開(kāi)辟了一塊數(shù)組的空間,而在函數(shù)調(diào)用結(jié)束后數(shù)組開(kāi)辟的空間已經(jīng)被回收了,str接收了p的值,而p所指向的空間已經(jīng)被回收了,所以p所指向的值也會(huì)發(fā)生變化,所以此時(shí)printf(str);打印的會(huì)是未知的結(jié)果
4.3練習(xí)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);
}請(qǐng)問(wèn)運(yùn)行Test 函數(shù)會(huì)有什么樣的結(jié)果?
Getmemory函數(shù)時(shí)傳址調(diào)用,將申請(qǐng)的動(dòng)態(tài)開(kāi)辟內(nèi)存空間的起始位置地址給了str,所以能夠正常訪問(wèn)開(kāi)辟的內(nèi)存。不過(guò)沒(méi)有進(jìn)行free會(huì)造成內(nèi)存泄漏的問(wèn)題。
4.4練習(xí)4
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}請(qǐng)問(wèn)運(yùn)行Test 函數(shù)會(huì)有什么樣的結(jié)果?
此時(shí)已經(jīng)str所指向的動(dòng)態(tài)內(nèi)存空間已經(jīng)釋放掉了,會(huì)造成非法訪問(wèn)。
5.C/C++程序的內(nèi)存開(kāi)辟

C/C++程序內(nèi)存分配的幾個(gè)區(qū)域:
1.棧區(qū):在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。棧區(qū)主要存放運(yùn)行函數(shù)而存放的局部變量、函數(shù)參數(shù)、返回?cái)?shù)據(jù)、返回地址等。
2.堆區(qū):一般有程序員分配,若程序員不釋放,程序結(jié)束時(shí)可能由OS回收,分配方式類似于鏈表。
3.數(shù)據(jù)段(靜態(tài)區(qū)):存放全局變量,靜態(tài)數(shù)據(jù),程序結(jié)束后由系統(tǒng)釋放。
4.代碼段:存放函數(shù)體的二進(jìn)制代碼
實(shí)際上普通的局部變量是在棧區(qū)分配空間的,棧區(qū)的特點(diǎn)是在上面創(chuàng)建的變量出了作用域就銷毀,但是被static修飾的變量存放在數(shù)據(jù)段,數(shù)據(jù)段的特點(diǎn)是,在上面創(chuàng)建的變量,直到程序結(jié)束才銷毀,所以static修飾的變量生命周期變長(zhǎng)了。
到此這篇關(guān)于C語(yǔ)言動(dòng)態(tài)內(nèi)存管理深入探討的文章就介紹到這了,更多相關(guān)C語(yǔ)言動(dòng)態(tài)內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解C語(yǔ)言中的動(dòng)態(tài)內(nèi)存管理
- 一文帶你搞懂C語(yǔ)言動(dòng)態(tài)內(nèi)存管理
- C語(yǔ)言動(dòng)態(tài)內(nèi)存管理malloc柔性數(shù)組示例詳解
- 詳解C語(yǔ)言中動(dòng)態(tài)內(nèi)存管理及柔性數(shù)組的使用
- 深入了解C語(yǔ)言的動(dòng)態(tài)內(nèi)存管理
- C語(yǔ)言中動(dòng)態(tài)內(nèi)存管理圖文詳解
- C語(yǔ)言深入細(xì)致講解動(dòng)態(tài)內(nèi)存管理
- C語(yǔ)言動(dòng)態(tài)內(nèi)存管理的原理及實(shí)現(xiàn)方法
相關(guān)文章
C++11中l(wèi)ambda、std::function和std:bind詳解
大家都知道C++11中增加了許多的新特性,下面在這篇文中我們就來(lái)聊一下lambda表達(dá)式,閉包,std::function以及std::bind。文中介紹的很詳細(xì),相信對(duì)大家具有一定的參考價(jià)值,有需要的朋友們下面來(lái)一起看看吧。2017-01-01
基于C++實(shí)現(xiàn)的哈夫曼編碼解碼操作示例
這篇文章主要介紹了基于C++實(shí)現(xiàn)的哈夫曼編碼解碼操作,結(jié)合實(shí)例形式分析了C++實(shí)現(xiàn)的哈夫曼編碼解碼相關(guān)定義與使用技巧,需要的朋友可以參考下2018-04-04
從匯編看c++函數(shù)的默認(rèn)參數(shù)的使用說(shuō)明
本篇文章介紹了,在c++中函數(shù)的默認(rèn)參數(shù)的使用說(shuō)明分析。需要的朋友參考下2013-05-05
C語(yǔ)言實(shí)現(xiàn)模擬銀行系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)模擬銀行系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
關(guān)于C++出現(xiàn)Bus error問(wèn)題的排查與解決
項(xiàng)目代碼中經(jīng)常出現(xiàn)莫名其妙的Bus error問(wèn)題,并且代碼中增加很多try catch 后依然不能將錯(cuò)誤捕獲,一旦Bus erro出現(xiàn),進(jìn)程直接崩潰掉,所以本文給大家介紹了關(guān)于C++出現(xiàn)Bus error問(wèn)題的排查與解決,需要的朋友可以參考下2024-01-01
C語(yǔ)言實(shí)現(xiàn)可增容動(dòng)態(tài)通訊錄詳細(xì)過(guò)程
這篇文章主要為大家介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易通訊錄的完整流程,此通訊錄還可以增容,并且每個(gè)環(huán)節(jié)都有完整代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-05-05
關(guān)于雙向鏈表的增刪改查和排序的C++實(shí)現(xiàn)
下面小編就為大家?guī)?lái)一篇關(guān)于雙向鏈表的增刪改查和排序的C++實(shí)現(xiàn)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12

