C語(yǔ)言中常見的六種動(dòng)態(tài)內(nèi)存錯(cuò)誤總結(jié)
1、對(duì)NULL指針的解引用操作
代碼:
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20; //如果p的值是NULL,就會(huì)有問(wèn)題
free(p);
}
分析:
- 首先看到第一個(gè),你要知道的是
INT_MAX是什么。它是一個(gè)宏定義,表示int類型(整型)能夠表示的最大值,其值為2147483647,那在上面講malloc的時(shí)候我們有說(shuō)到過(guò),若是需要申請(qǐng)的空間過(guò)大的話可能就會(huì)導(dǎo)致申請(qǐng)失敗的問(wèn)題,所以這里很致命的一個(gè)錯(cuò)誤就是在申請(qǐng)空間之后沒有去及時(shí)判斷是否申請(qǐng)成功 - 可以看到編譯器也是給我們報(bào)出了一個(gè)Warning警告說(shuō):取消對(duì)NULL指針的引用

改進(jìn):
- 此時(shí)我們就可以對(duì)代碼去做一個(gè)改進(jìn),對(duì)malloc之后的返回值做一個(gè)判斷
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
if (NULL == p)
{
perror("fail malloc");
exit(-1);
}
*p = 20;//如果p的值是NULL,就會(huì)有問(wèn)題
free(p);
}
- 這個(gè)時(shí)候我們就可以看到?jīng)]有警告再報(bào)出來(lái)了

2、對(duì)動(dòng)態(tài)開辟空間的越界訪問(wèn)
代碼:
int main(void)
{
int* p = (int*)malloc(100);
if (NULL == p)
{
perror("malloc fail");
exit(-1);
}
int i = 0;
for (int i = 0; i < 100; i++)
{
*(p + i) = 0; // 當(dāng)i == 25時(shí)便會(huì)越界
}
free(p);
p = NULL;
return 0;
}
分析:
- 接下去我們來(lái)看這個(gè)越界訪問(wèn)的問(wèn)題,首先我們使用
malloc向堆區(qū)申請(qǐng)了100個(gè)字節(jié)的空間,但是呢在下面對(duì)這塊空間進(jìn)行訪問(wèn)的時(shí)候卻訪問(wèn)了100個(gè)整型的大小,此時(shí)一定會(huì)造成訪問(wèn)越界的問(wèn)題 - 但是呢口說(shuō)無(wú)憑,我們一樣通過(guò)調(diào)試來(lái)進(jìn)行一個(gè)觀察,不過(guò)這里在進(jìn)行循環(huán)的時(shí)候
i沒有到100的話是不會(huì)出問(wèn)題的,所以為了方便調(diào)試我們需要去設(shè)置一個(gè)【條件斷點(diǎn)】,將i從【24】開始執(zhí)行,這樣我們很快就能觀察到結(jié)果了

- 然后我們便可以通過(guò)調(diào)試去進(jìn)行觀察了,可以看到
i并沒有到達(dá)100,而是直接跳出了當(dāng)前循環(huán),然后在free()的時(shí)候就出現(xiàn)了問(wèn)題,一般我們?cè)谝恍┢渌胤接^察不到的問(wèn)題就會(huì)在free()的地方顯現(xiàn)出來(lái),因?yàn)榇藭r(shí)是要去釋放掉我們的這塊申請(qǐng)的空間了,便會(huì)引發(fā)一些異常

其實(shí)我們可以將*(p + i) = 0修改成p[i] = 0,利用[]操作符對(duì)某個(gè)下標(biāo)進(jìn)行訪問(wèn),此時(shí)我們可以看到編譯器就報(bào)出了警告說(shuō)索引"99"超出了“0"至”24"的有效范圍,因此100個(gè)字節(jié)的空間只能供25個(gè)整型來(lái)進(jìn)行存放,因此合法的下標(biāo)索引即為0 ~ 24

改進(jìn):
- 代碼修改這一塊的話我們只需要在申請(qǐng)空間的時(shí)候保證申請(qǐng)到足夠的、正確的容量即可
int* p = (int*)malloc(100 * sizeof(int));
這個(gè)時(shí)候我們就可以看到?jīng)]有警告再報(bào)出來(lái)了

3、對(duì)非動(dòng)態(tài)開辟內(nèi)存進(jìn)行free釋放
代碼:
void test()
{
int a = 10;
int* p = &a;
free(p); //ok?
}
分析:
- 接下去再來(lái)看第三個(gè),這里是對(duì)非動(dòng)態(tài)開辟的內(nèi)存進(jìn)行
free()釋放,那我們?cè)诮榻Bfree()的時(shí)候說(shuō)到它只能釋放由【malloc】、【calloc】、【realloc】所開辟出來(lái)的空間,這些空間都是在堆區(qū)上進(jìn)行申請(qǐng)的,但是我們?cè)谄胀ǖ暮瘮?shù)中所創(chuàng)建的普通變量無(wú)非是棧區(qū)或者靜態(tài)區(qū)的,它們的釋放工作并不是由free()來(lái)完成的,因此強(qiáng)行去這樣做的話就會(huì)造成了一個(gè)很大的問(wèn)題 - 可以看到一樣出現(xiàn)了我們剛才那樣類似的問(wèn)題

改進(jìn):
- 本代碼并沒有什么通用的改進(jìn)辦法,如果不想出現(xiàn)問(wèn)題的話就不要
free()普通棧區(qū)上的變量即可,或者按照常規(guī)去動(dòng)態(tài)申請(qǐng)然后在進(jìn)行free()
4、使用free釋放一塊動(dòng)態(tài)開辟內(nèi)存的一部分
代碼:
void test()
{
int* p = (int*)malloc(100);
if (NULL == p)
{
perror("malloc fail");
exit(-1);
}
for (int i = 0; i < 10; i++)
{
p++;
}
free(p); //p不再指向動(dòng)態(tài)內(nèi)存的起始位置
}
分析:
- 本題的情境是這樣的,我們?cè)诙褏^(qū)申請(qǐng)了100個(gè)字節(jié)后,讓指針p指向這塊地址的起始位置,然后讓其偏移了10個(gè)整型的位置,即40B的大小,那么此時(shí)指針p其實(shí)就指向了當(dāng)前這一塊地址的中間位置,那么此時(shí)再去
free的時(shí)候其實(shí)就會(huì)出問(wèn)題 - 因?yàn)樵摵瘮?shù)在釋放動(dòng)態(tài)申請(qǐng)的內(nèi)存時(shí)需要從這塊地址其實(shí)位置開始,然后釋放制定的字節(jié)數(shù),若是從某個(gè)中間位置開始的話就不對(duì)了
從下圖可以看出,因?yàn)?code>free()函數(shù)需要做到申請(qǐng)多少釋放多少,所以當(dāng)其釋放了一部分之后,就不夠了,便造成了訪問(wèn)內(nèi)存錯(cuò)誤的問(wèn)題

- 一樣,我們通過(guò)調(diào)試去進(jìn)行觀察,首先在一開始申請(qǐng)出這塊空間的時(shí)候先記錄一下初始位置的地址,然后我們便可以觀察到其進(jìn)行了一個(gè)偏移,

- 可以看到,此時(shí)若是去
free()的話就會(huì)出現(xiàn)警告,很明顯這個(gè)debug_heap.cpp就是【堆】這一塊出的問(wèn)題

改進(jìn):
- 要如何改進(jìn)的話就會(huì)不要去
free()一塊動(dòng)態(tài)開辟出來(lái)內(nèi)存的一部分,而是要從起始地址開始釋放,申請(qǐng)多少釋放多少
5、對(duì)同一塊動(dòng)態(tài)內(nèi)存多次釋放
代碼:
void test()
{
int* p = (int*)malloc(100);
//使用...
free(p);
//...
free(p); //重復(fù)釋放
}
分析:
- 這一點(diǎn)的話就是在我們釋放完一塊內(nèi)存空間后忘了,然后再去對(duì)其進(jìn)行了一次釋放,這種操作的話其實(shí)也是很危險(xiǎn)的,當(dāng)我們?cè)诘谝淮吾尫诺臅r(shí)候p所指向的那塊空間的使用權(quán)已經(jīng)還給操作系統(tǒng)了,但是呢我們并沒有對(duì)這個(gè)指針
p做置空的操作,于是它還指向那塊空間所在的地址,不過(guò)里面的內(nèi)容已經(jīng)是隨機(jī)的了,那么這個(gè)指針就是一個(gè)【野指針】 - 此時(shí)再對(duì)其做一個(gè)
free()的操作,就會(huì)造成操作野指針的問(wèn)題

改進(jìn):
- 此時(shí)我們就可以對(duì)代碼去做一個(gè)簡(jiǎn)單的改進(jìn),在第一次
free后將指針p置為NULL即可,此刻若是后面再去free的話,就不會(huì)出現(xiàn)問(wèn)題了,因?yàn)楫?dāng)我們傳遞NULL作為參數(shù)的時(shí)候,free(NULL)便不會(huì)去做任何的事情
void test()
{
int* p = (int*)malloc(100);
//使用...
free(p);
p = NULL; // 將不使用的指針置為NULL
//...
free(p); //重復(fù)釋放
}
6、動(dòng)態(tài)開辟內(nèi)存忘記釋放(內(nèi)存泄漏)
代碼:
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
}
分析:
- 那最后一個(gè)呢就是我們最常見的,在動(dòng)態(tài)開辟內(nèi)存后忘記去釋放了,例如上面有一個(gè)
test()函數(shù),函數(shù)內(nèi)部去申請(qǐng)了100個(gè)字節(jié)的數(shù)據(jù),并為其做了一個(gè)初始化,此時(shí)main函數(shù)就正常地去調(diào)用它,但是呢這中間卻沒有任何地free()釋放操作,就會(huì)存在【內(nèi)存泄漏】的問(wèn)題
?? 那有同學(xué)說(shuō):既然函數(shù)內(nèi)部沒有做釋放的話我在調(diào)用結(jié)束后去free一下這個(gè)p不就好了
這句話其實(shí)就存在很大的問(wèn)題,、對(duì)于一個(gè)在一個(gè)函數(shù)創(chuàng)建的變量,是處在當(dāng)前這個(gè)函數(shù)所維護(hù)的棧幀中的,所以當(dāng)這個(gè)函數(shù)調(diào)用結(jié)束后局部變量就會(huì)隨著棧幀的銷毀而不復(fù)存在,那此時(shí)我們?cè)傧肴?code>free()釋放這塊空間的時(shí)候,是無(wú)法訪問(wèn)到這個(gè)指針p的。因此要釋放的話只能在函數(shù)內(nèi)部進(jìn)行才可以
改進(jìn):
- 那改進(jìn)這一塊的話我們只需要在函數(shù)調(diào)用結(jié)束前去將其釋放即可,不過(guò)別忘了在
free()之后要將指針置為NULL防止野指針
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
free(p);
p = NULL;
}
- 所以當(dāng)我們?cè)谑褂脛?dòng)態(tài)內(nèi)存的時(shí)候,一定要保證在【malloc】之后及時(shí)【free】,此時(shí)才能保證不會(huì)內(nèi)存泄漏
但是它們兩個(gè)成對(duì)出現(xiàn)就一定不會(huì)出現(xiàn)問(wèn)題嗎?
- 我們來(lái)看看下面這段代碼,可以看到中間有一個(gè)
if(1)的條件判斷,我們知道這個(gè)條件是天然成立的,然后看到當(dāng)這個(gè)條件成立后就會(huì)執(zhí)行return語(yǔ)句,那么當(dāng)前這個(gè)函數(shù)就會(huì)結(jié)束了,此時(shí)并沒有運(yùn)行到free(p)這句話 - 那么聰明的你一定很快反應(yīng)過(guò)來(lái)了,即使是存在【malloc】和【free】成對(duì)出現(xiàn)的情況下,可能也無(wú)法百分百保證不會(huì)產(chǎn)生內(nèi)存泄漏的問(wèn)題,所以還是需要我們?cè)趯懗绦虻臅r(shí)候多注意細(xì)節(jié)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
if (1)
return; // 因?yàn)槟承l件中途return了, 沒到free()
free(p);
}
int main()
{
test();
}
總結(jié)與提煉
最后來(lái)總結(jié)一下本文所學(xué)習(xí)的內(nèi)容
- 通過(guò)上面的六個(gè)案例,我們總共了解到了六種動(dòng)態(tài)內(nèi)存錯(cuò)誤的形式,分別是
- 【對(duì)NULL指針的解引用操作】 —— 操作空指針是非常危險(xiǎn)的一件事,記得判空哦
- 【對(duì)動(dòng)態(tài)開辟空間的越界訪問(wèn)】 —— 有多少就拿多少,不要貪心哦
- 【對(duì)非動(dòng)態(tài)開辟內(nèi)存進(jìn)行free釋放】 —— 請(qǐng)正確分類,送它去該去的地方
- 【使用free釋放一塊動(dòng)態(tài)開辟內(nèi)存的一部分 】—— 借了多少還多少,不要私藏哦
- 【對(duì)同一塊動(dòng)態(tài)內(nèi)存多次釋放】 —— 借了多少還多少,不要私藏哦
- 【動(dòng)態(tài)開辟內(nèi)存忘記釋放】 —— 借了別人的東西要記得還
- 在使用動(dòng)態(tài)內(nèi)存函數(shù)開辟出空間后,使用的時(shí)候一定要牢記以上幾點(diǎn),否則要出大問(wèn)題的!
以上就是C語(yǔ)言中常見的六種動(dòng)態(tài)內(nèi)存錯(cuò)誤總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于C語(yǔ)言動(dòng)態(tài)內(nèi)存錯(cuò)誤的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 深入了解C語(yǔ)言的動(dòng)態(tài)內(nèi)存管理
- 詳解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ǔ)言中的動(dòng)態(tài)內(nèi)存管理
- C語(yǔ)言動(dòng)態(tài)內(nèi)存分配圖文講解
- 使用c語(yǔ)言輕松實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管
- 一文帶你了解C語(yǔ)言中的動(dòng)態(tài)內(nèi)存管理函數(shù)
- C語(yǔ)言動(dòng)態(tài)內(nèi)存管理的原理及實(shí)現(xià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)存管理的實(shí)現(xiàn)示例
相關(guān)文章
C語(yǔ)言獲取Shell返回結(jié)果的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇C語(yǔ)言獲取Shell返回結(jié)果的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07
C語(yǔ)言標(biāo)準(zhǔn)時(shí)間與秒單位相互轉(zhuǎn)換
這篇文章主要介紹了C語(yǔ)言標(biāo)準(zhǔn)時(shí)間與秒單位相互轉(zhuǎn)換,秒單位與標(biāo)準(zhǔn)時(shí)間的轉(zhuǎn)換方式,這份代碼一般用在嵌入式單片機(jī)里比較多,比如:設(shè)置RTC時(shí)鐘的時(shí)間,從RTC里讀取秒單位時(shí)間后,需要轉(zhuǎn)換成標(biāo)準(zhǔn)時(shí)間顯示。下文分享需要的小伙伴可以參考一下2022-05-05
基于C語(yǔ)言構(gòu)建一個(gè)獨(dú)立棧協(xié)程和共享?xiàng)f(xié)程的任務(wù)調(diào)度系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了如何基于C語(yǔ)言構(gòu)建一個(gè)獨(dú)立棧協(xié)程和共享?xiàng)f(xié)程的任務(wù)調(diào)度系統(tǒng),文中的示例代碼講解詳細(xì),需要的可以參考下2024-02-02
解決VC++編譯報(bào)錯(cuò)error C2248的方案
這篇文章主要介紹了解決VC++編譯報(bào)錯(cuò)error C2248的方案的相關(guān)資料,需要的朋友可以參考下2015-11-11
C++編寫實(shí)現(xiàn)圖書管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++編寫實(shí)現(xiàn)圖書管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

