C語言常見內(nèi)存錯(cuò)誤及解決方法
關(guān)于內(nèi)存的一些知識已在內(nèi)存分配中提及,現(xiàn)記錄與分享常見的內(nèi)存錯(cuò)誤與對策。
類型 1:內(nèi)存未分配成功,卻使用了它。
方 法:在使用之前檢查指針是否為NULL。
1)當(dāng)指針p是函數(shù)的參數(shù)時(shí),在函數(shù)入口處用語句assert(p!=NULL)進(jìn)行斷言檢查。
2)當(dāng)使用malloc或new來申請內(nèi)存時(shí),應(yīng)該用if(p != NULL)進(jìn)行防錯(cuò)檢查。
類型 2:引用了尚未初始化的指針
原 因:內(nèi)存的缺省初始值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn),在使用之前都進(jìn)行初始化。
1)沒有初始化的觀念。
2)內(nèi)存的缺省值是未定義,即垃圾值。
類型 3:越界操作內(nèi)存
原 因:內(nèi)存分配成功且初始了,但越界操作是不允許的。
例 如:在使用數(shù)組時(shí)經(jīng)常發(fā)生下標(biāo)“多1”或“少1”,特別是在for循環(huán)語句時(shí)。
類型 4:忘記釋放內(nèi)存,造成內(nèi)存泄漏。
原 因:含有這種類型錯(cuò)誤的函數(shù),每被調(diào)用一次,就丟失一塊內(nèi)存。當(dāng)內(nèi)存充足時(shí)看不到這種錯(cuò)誤帶來的影響,當(dāng)內(nèi)存耗盡時(shí)系統(tǒng)提示:“內(nèi)存耗盡”。因此,動態(tài)內(nèi)存的申請與釋放必須配對,程序中malloc與free的使用次數(shù)要相同。
類型 5:釋放了內(nèi)存卻繼續(xù)使用它
原 因:對應(yīng)的情況有2種
1)返回了“棧內(nèi)存的指針或引用”,因?yàn)槎褩V械淖兞吭诤瘮?shù)結(jié)束后自動銷毀。
2)某塊內(nèi)存被free后,沒有將指向該內(nèi)存的指針設(shè)置為NULL,導(dǎo)致產(chǎn)生“野指針”。
使用規(guī)則為了保證代碼的健壯和安全,可以參考如下的規(guī)則:
規(guī)則1:使用malloc申請的內(nèi)存時(shí),必須要立即檢查相對應(yīng)的指針是否為NULL。
規(guī)則2:初始化數(shù)組和動態(tài)內(nèi)存。
規(guī)則3:避免數(shù)組或指針下標(biāo)越界。
規(guī)則4:動態(tài)內(nèi)存的申請和釋放必須相配對,防止內(nèi)存泄漏。
規(guī)則5:free釋放某塊內(nèi)存之后,要立即將指針設(shè)置為NULL,防止產(chǎn)生野指針。
幾個(gè)重要的概念:
1.野指針概念:“野指針”不是NULL指針,是指指向“垃圾”內(nèi)存的指針。即指針指向的內(nèi)容是不確定的。
產(chǎn)生的原因:1)指針變量沒有初始化。因此,創(chuàng)建指針變量時(shí),該變量要被置為NULL或者指向合法的內(nèi)存單元。
2)指針p被free之后,沒有置為NULL,讓人誤以為p是個(gè)合法的指針。
3)指針跨越合法范圍操作。不要返回指向棧內(nèi)存的指針或引用
例子1-1:引用尚未初始化的指針
[cpp] view plain copy
char *p;
*p = 'A';//error,p指向未定義
例子1-2:return語句返回指向“棧內(nèi)存”的指針
[cpp] view plain copy
char *GetString1(void)
{
char p[] = "hello world!";
//p在棧區(qū),常量字符串在常量字符區(qū)
return p;//error,返回棧內(nèi)存的地址
}
char p[] =
一個(gè)數(shù)組,這個(gè)數(shù)組是局部變量。
char* p =
一個(gè)指針,這個(gè)指針指向一個(gè)字符串常量
區(qū)別在于:數(shù)組的話,字符串是存在于這個(gè)數(shù)組里的,因?yàn)檫@個(gè)數(shù)組屬于局部變量,所以你就算把數(shù)組的地址返回給主函數(shù),主函數(shù)也沒有辦法再訪問這個(gè)地址了。
但是如果是指向字符串常量的指針,這個(gè)字符串是放在程序的常量區(qū)而不是放在局部變量中,那么你把這個(gè)常量的地址返回給主函數(shù),主函數(shù)也還是可以訪問它的。
char* p是一個(gè)指針,根本沒分配內(nèi)存,他指向的"hello world" 是一個(gè)地址,而且地址不用加“”
而char p[]是一個(gè)數(shù)組,已經(jīng)分配內(nèi)存,是將"hello world" 復(fù)制到該內(nèi)存里面,這個(gè)內(nèi)存是可讀寫的
例子1-3:使用了被釋放的內(nèi)存
[cpp] view plain copy
char *pstr = (char *)malloc(sizeof(char)*100);
free(pstr); //pstr所指的內(nèi)存被釋放
if (NULL !=pstr)//沒起到作用
{
strcpy(pstr,"string!");//error,有時(shí)候程序不會提示有誤,但還是不允許
}
注意:free()釋放的是指針指向的內(nèi)存!不是指針變量!這點(diǎn)非常非常重要!指針是一個(gè)變量,只有程序結(jié)束時(shí)才被銷毀。釋放了內(nèi)存空間后,原來指向這塊空間的指針還是存在!只不過現(xiàn)在指針指向的內(nèi)容的垃圾,是未定義的,所以說是垃圾。因此,前面我已經(jīng)說過了,釋放內(nèi)存后把指針指向NULL,防止指針在后面不小心又被解引用。
對比下面的例子,加深理解
例子1-4:函數(shù)返回值傳遞動態(tài)內(nèi)存
[cpp] view plain copy
char* GetMemory(int num)
{
char *p = (char *)malloc(sizeof(char) * num);
return p ;//ok,返回堆區(qū)的地址值
}
例子1-5:
2.內(nèi)存泄漏[cpp] view plain copy
char *GetString(void)
{
char *p = "hello world!";
//指針變量p在棧區(qū),指向文字常量區(qū)的字符
return p;//ok,返回字符串的地址
}
概念:用動態(tài)內(nèi)存分配函數(shù)動態(tài)開辟的空間,在使用完畢后未釋放,結(jié)果導(dǎo)致一直占據(jù)該內(nèi)存單元,直到程序結(jié)束。
注意:內(nèi)存泄漏是指堆內(nèi)存的泄漏。它的一般表現(xiàn)方式是程序運(yùn)行時(shí)間越長,占用內(nèi)存越多,最終用盡全部內(nèi)存,整個(gè)系統(tǒng)崩潰。
例子2-1:內(nèi)存泄漏,共9*100字節(jié)發(fā)生泄漏
3.內(nèi)存溢出[cpp] view plain copy
void Test(void)
{
char *p = NULL;
for ( int i = 0; i<10; i++)
{
p = (char*)malloc(100);//沒循環(huán)一次內(nèi)存泄漏一塊,最后一次得到正確使用
}
strncpy(p,"string!");
free(p);
}
概念:系統(tǒng)分配的內(nèi)存不足以放下數(shù)據(jù),稱為內(nèi)存溢出。
例子3-1:運(yùn)行時(shí)提示出錯(cuò)
[cpp] view plain copy
char str[10]={0};
strcpy(str,"hello world!");//error!
https://blog.csdn.net/qq_38211852/article/details/80085591
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請聯(lián)系工作人員刪除。