常見(jiàn)的C語(yǔ)言?xún)?nèi)存錯(cuò)誤及對(duì)策_(dá)第1頁(yè)
常見(jiàn)的C語(yǔ)言?xún)?nèi)存錯(cuò)誤及對(duì)策_(dá)第2頁(yè)
常見(jiàn)的C語(yǔ)言?xún)?nèi)存錯(cuò)誤及對(duì)策_(dá)第3頁(yè)
常見(jiàn)的C語(yǔ)言?xún)?nèi)存錯(cuò)誤及對(duì)策_(dá)第4頁(yè)
常見(jiàn)的C語(yǔ)言?xún)?nèi)存錯(cuò)誤及對(duì)策_(dá)第5頁(yè)
已閱讀5頁(yè),還剩5頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、常見(jiàn)的C 語(yǔ)言?xún)?nèi)存錯(cuò)誤及對(duì)策一、指針沒(méi)有指向一塊合法的內(nèi)存定義了指針變量,但是沒(méi)有為指針?lè)峙鋬?nèi)存,即指針沒(méi)有指向一塊合法的內(nèi)存。淺顯的例子就不舉了,這里舉幾個(gè)比較隱蔽的例子。1、結(jié)構(gòu)體成員指針未初始化struct studentchar *name;int score;stu,*pstu;intmain(strcpy(,"Jimy"stu.score =99;return 0; 很多初學(xué)者犯了這個(gè)錯(cuò)誤還不知道是怎么回事。這里定義了結(jié)構(gòu)體變量stu ,但是他沒(méi)想到這個(gè)結(jié)構(gòu)體內(nèi)部char *name這成員在定義結(jié)構(gòu)體變量stu 時(shí),只是給name 這個(gè)指針變量本

2、身分配了4個(gè)字節(jié)。name 指針并沒(méi)有指向一個(gè)合法的地址,這時(shí)候其內(nèi)部存的只是一些亂碼。所以在調(diào)用strcpy 函數(shù)時(shí),會(huì)將字符串"Jimy" 往亂碼所指的內(nèi)存上拷貝,而這塊內(nèi)存name 指針根本就無(wú)權(quán)訪(fǎng)問(wèn),導(dǎo)致出錯(cuò)。解決的辦法是為name 指針malloc 一塊空間。同樣,也有人犯如下錯(cuò)誤:intmain(pstu =(structstudent*malloc(sizeof(structstudent; strcpy(pstu->name,"Jimy"pstu->score=99;free(pstu;return 0;為指針變量pstu 分

3、配了內(nèi)存,但是同樣沒(méi)有給name 指針?lè)峙鋬?nèi)存。錯(cuò)誤與上面第一種情況一樣,解決的辦法也一樣。這里用了一個(gè)malloc 給人一種錯(cuò)覺(jué),以為也給name 指針?lè)峙淞藘?nèi)存。2、沒(méi)有為結(jié)構(gòu)體指針?lè)峙渥銐虻膬?nèi)存intmain(pstu =(structstudent*malloc(sizeof(structstudent*;strcpy(pstu->name,"Jimy"pstu->score=99;free(pstu;return 0; 為pstu 分配內(nèi)存的時(shí)候,分配的內(nèi)存大小不合適。這里把sizeof(structstudent 誤寫(xiě)為sizeof(structst

4、udent*。當(dāng)然name 指針同樣沒(méi)有被分配內(nèi)存。解決辦法同上。3、函數(shù)的入口校驗(yàn)不管什么時(shí)候,我們使用指針之前一定要確保指針是有效的。一般在函數(shù)入口處使用assert(NULL!=p 對(duì)參數(shù)進(jìn)行校驗(yàn)。在非參數(shù)的地方使用if (NULL !=p )來(lái)校驗(yàn)。但這都有一個(gè)要求,即p 在定義的同時(shí)被初始化為NULL 了。比如上面的例子,即使用if (NULL !=p )校驗(yàn)也起不了作用,因?yàn)閚ame 指針并沒(méi)有被初始化為NULL ,其內(nèi)部是一個(gè)非NULL 的亂碼。assert 是一個(gè)宏,而不是函數(shù),包含在assert.h 頭文件中。如果其后面括號(hào)里的值為假,則程序終止運(yùn)行,并提示出錯(cuò);如果后面括號(hào)

5、里的值為真,則繼續(xù)運(yùn)行后面的代碼。這個(gè)宏只在Debug 版本上起作用,而在Release 版本被編譯器完全優(yōu)化掉,這樣就不會(huì)影響代碼的性能。有人也許會(huì)問(wèn),既然在Release 版本被編譯器完全優(yōu)化掉,那Release 版本是不是就完全沒(méi)有這個(gè)參數(shù)入口校驗(yàn)了呢?這樣的話(huà)那不就跟不使用它效果一樣嗎?是的,使用assert 宏的地方在Release 版本里面確實(shí)沒(méi)有了這些校驗(yàn)。但是我們要知道,assert 宏只是幫助我們調(diào)試代碼用的,它的一切作用就是讓我們盡可能的在調(diào)試函數(shù)的時(shí)候把錯(cuò)誤排除掉,而不是等到Release 之后。它本身并沒(méi)有除錯(cuò)功能。再有一點(diǎn)就是,參數(shù)出現(xiàn)錯(cuò)誤并非本函數(shù)有問(wèn)題,而是調(diào)用者

6、傳過(guò)來(lái)的實(shí)參有問(wèn)題。assert 宏可以幫助我們定位錯(cuò)誤,而不是排除錯(cuò)誤。二、為指針?lè)峙涞膬?nèi)存太小為指針?lè)峙淞藘?nèi)存,但是內(nèi)存大小不夠,導(dǎo)致出現(xiàn)越界錯(cuò)誤。char *p1=“abcdefg”;char *p2=(char*malloc(sizeof(char*strlen(p1;strcpy(p2,p1;p1是字符串常量,其長(zhǎng)度為7個(gè)字符,但其所占內(nèi)存大小為8個(gè)byte 。初學(xué)者往往忘了字符串常量的結(jié)束標(biāo)志“0”。這樣的話(huà)將導(dǎo)致p1字符串中最后一個(gè)空字符“0”沒(méi)有被拷貝到p2中。解決的辦法是加上這個(gè)字符串結(jié)束標(biāo)志符:char *p2=(char*malloc(sizeof(char*strlen

7、(p1+1*sizeof(char;這里需要注意的是,只有字符串常量才有結(jié)束標(biāo)志符。比如下面這種寫(xiě)法就沒(méi)有結(jié)束標(biāo)志符了:char a7=a,b,c,d,e,f,g;另外,不要因?yàn)閏har 類(lèi)型大小為1個(gè)byte 就省略sizof (char )這種寫(xiě)法。這樣只會(huì)使你的代碼可移植性下降。三、內(nèi)存分配成功,但并未初始化犯這個(gè)錯(cuò)誤往往是由于沒(méi)有初始化的概念或者是以為內(nèi)存分配好之后其值自然為0。未初始化指針變量也許看起來(lái)不那么嚴(yán)重,但是它確確實(shí)實(shí)是個(gè)非常嚴(yán)重的問(wèn)題,而且往往出現(xiàn)這種錯(cuò)誤很難找到原因。曾經(jīng)有一個(gè)學(xué)生在寫(xiě)一個(gè)windows 程序時(shí),想調(diào)用字庫(kù)的某個(gè)字體。而調(diào)用這個(gè)字庫(kù)需要填充一個(gè)結(jié)構(gòu)體。他

8、很自然的定義了一個(gè)結(jié)構(gòu)體變量,然后把他想要的字庫(kù)代碼賦值給了相關(guān)的變量。但是,問(wèn)題就來(lái)了,不管怎么調(diào)試,他所需要的這種字體效果總是不出來(lái)。我在檢查了他的代碼之后,沒(méi)有發(fā)現(xiàn)什么問(wèn)題,于是單步調(diào)試。在觀察這個(gè)結(jié)構(gòu)體變量的內(nèi)存時(shí),發(fā)現(xiàn)有幾個(gè)成員的值為亂碼。就是其中某一個(gè)亂碼惹得禍!因?yàn)橄到y(tǒng)會(huì)按照這個(gè)結(jié)構(gòu)體中的某些特定成員的值去字庫(kù)中尋找匹配的字體,當(dāng)這些值與字庫(kù)中某種字體的某些項(xiàng)匹配時(shí),就調(diào)用這種字體。但是很不幸,正是因?yàn)檫@幾個(gè)亂碼,導(dǎo)致沒(méi)有找到相匹配的字體!因?yàn)橄到y(tǒng)并無(wú)法區(qū)分什么數(shù)據(jù)是亂碼,什么數(shù)據(jù)是有效的數(shù)據(jù)。只要有數(shù)據(jù),系統(tǒng)就理所當(dāng)然的認(rèn)為它是有效的。也許這種嚴(yán)重的問(wèn)題并不多見(jiàn),但是也絕不能掉

9、以輕心。所以在定義一個(gè)變量時(shí),第一件事就是初始化。你可以把它初始化為一個(gè)有效的值,比如:int i =10;char *p=(char*malloc(sizeof(char;但是往往這個(gè)時(shí)候我們還不確定這個(gè)變量的初值,這樣的話(huà)可以初始化為0或NULL 。int i =0;char *p=NULL ;如果定義的是數(shù)組的話(huà),可以這樣初始化:int a10=0;或者用memset 函數(shù)來(lái)初始化為0:memset (a,0,sizeof(a);memset 函數(shù)有三個(gè)參數(shù),第一個(gè)是要被設(shè)置的內(nèi)存起始地址;第二個(gè)參數(shù)是要被設(shè)置的值;第三個(gè)參數(shù)是要被設(shè)置的內(nèi)存大小,單位為byte 。這里并不想過(guò)多的討論m

10、emset 函數(shù)的用法,如果想了解更多,請(qǐng)參考相關(guān)資料。至于指針變量如果未被初始化,會(huì)導(dǎo)致if 語(yǔ)句或assert 宏校驗(yàn)失敗。這一點(diǎn),上面已有分析。四、內(nèi)存越界內(nèi)存分配成功,且已經(jīng)初始化,但是操作越過(guò)了內(nèi)存的邊界。這種錯(cuò)誤經(jīng)常是由于操作數(shù)組或指針時(shí)出現(xiàn)“多1”或“少1”。比如:int a10=0;for (i=0;i<=10;i+ai=i;所以,for 循環(huán)的循環(huán)變量一定要使用半開(kāi)半閉的區(qū)間,而且如果不是特殊情況,循環(huán)變量盡量從0開(kāi)始。五、內(nèi)存泄漏內(nèi)存泄漏幾乎是很難避免的,不管是老手還是新手,都存在這個(gè)問(wèn)題。甚至包括windows ,Linux 這類(lèi)軟件,都或多或少有內(nèi)存泄漏。也許對(duì)于

11、一般的應(yīng)用軟件來(lái)說(shuō),這個(gè)問(wèn)題似乎不是那么突出,重啟一下也不會(huì)造成太大損失。但是如果你開(kāi)發(fā)的是嵌入式系統(tǒng)軟件呢?比如汽車(chē)制動(dòng)系統(tǒng),心臟起搏器等對(duì)安全要求非常高的系統(tǒng)。你總不能讓心臟起搏器重啟吧,人家閻王老爺是非常好客的。會(huì)產(chǎn)生泄漏的內(nèi)存就是堆上的內(nèi)存(這里不討論資源或句柄等泄漏情況),也就是說(shuō)由malloc 系列函數(shù)或new 操作符分配的內(nèi)存。如果用完之后沒(méi)有及時(shí)free 或delete ,這塊內(nèi)存就無(wú)法釋放,直到整個(gè)程序終止。1、告老還鄉(xiāng)求良田怎么去理解這個(gè)內(nèi)存分配和釋放過(guò)程呢?先看下面這段對(duì)話(huà):萬(wàn)歲爺:愛(ài)卿,你為朕立下了汗馬功勞,想要何賞賜啊?某功臣:萬(wàn)歲,黃金白銀,臣視之如糞土。臣年歲已老

12、,欲告老還鄉(xiāng)。臣乞良田千畝以蔭后世,別無(wú)他求。萬(wàn)歲爺:愛(ài)卿,你勞苦功高,卻僅要如此小賞,朕今天就如你所愿。戶(hù)部劉侍郎,查看湖廣一帶是否還有千畝上等良田未曾封賞。劉侍郎:長(zhǎng)沙尚有五萬(wàn)余畝上等良田未曾封賞。萬(wàn)歲爺:在長(zhǎng)沙撥良田千畝封賞愛(ài)卿。愛(ài)卿,良田千畝,你欲何用啊?某功臣:謝萬(wàn)歲。長(zhǎng)沙一帶,適合種水稻,臣想用來(lái)種水稻。種水稻需要把田分為一畝一塊,方便耕種。2、如何使用malloc 函數(shù)不要莫名其妙,其實(shí)上面這段小小的對(duì)話(huà),就是malloc 的使用過(guò)程。malloc 是一個(gè)函數(shù),專(zhuān)門(mén)用來(lái)從堆上分配內(nèi)存。使用malloc 函數(shù)需要幾個(gè)要求:內(nèi)存分配給誰(shuí)?這里是把良田分配給某功臣。分配多大內(nèi)存?這里是

13、分配一千畝。是否還有足夠內(nèi)存分配?這里是還有足夠良田分配。內(nèi)存的將用來(lái)存儲(chǔ)什么格式的數(shù)據(jù),即內(nèi)存用來(lái)做什么?這里是用來(lái)種水稻,需要把田分成一畝一塊。分配好的內(nèi)存在哪里?這里是在長(zhǎng)沙。如果這五點(diǎn)都確定,那內(nèi)存就能分配。下面先看malloc 函數(shù)的原型:(void*malloc(intsizemalloc 函數(shù)的返回值是一個(gè)void 類(lèi)型的指針,參數(shù)為int 類(lèi)型數(shù)據(jù),即申請(qǐng)分配的內(nèi)存大小,單位是byte 。內(nèi)存分配成功之后,malloc 函數(shù)返回這塊內(nèi)存的首地址。你需要一個(gè)指針來(lái)接收這個(gè)地址。但是由于函數(shù)的返回值是void *類(lèi)型的,所以必須強(qiáng)制轉(zhuǎn)換成你所接收的類(lèi)型。也就是說(shuō),這塊內(nèi)存將要用來(lái)存

14、儲(chǔ)什么類(lèi)型的數(shù)據(jù)。比如:char *p=(char*malloc(100;在堆上分配了100個(gè)字節(jié)內(nèi)存,返回這塊內(nèi)存的首地址,把地址強(qiáng)制轉(zhuǎn)換成char *類(lèi)型后賦給char *類(lèi)型的指針變量p 。同時(shí)告訴我們這塊內(nèi)存將用來(lái)存儲(chǔ)char 類(lèi)型的數(shù)據(jù)。也就是說(shuō)你只能通過(guò)指針變量p 來(lái)操作這塊內(nèi)存。這塊內(nèi)存本身并沒(méi)有名字,對(duì)它的訪(fǎng)問(wèn)是匿名訪(fǎng)問(wèn)。上面就是使用malloc 函數(shù)成功分配一塊內(nèi)存的過(guò)程。但是,每次你都能分配成功嗎?不一定。上面的對(duì)話(huà),皇帝讓?xiě)舨渴汤刹樵?xún)是否還有足夠的良田未被分配出去。使用malloc 函數(shù)同樣要注意這點(diǎn):如果所申請(qǐng)的內(nèi)存塊大于目前堆上剩余內(nèi)存塊(整塊),則內(nèi)存分配會(huì)失敗,函

15、數(shù)返回NULL 。注意這里說(shuō)的“堆上剩余內(nèi)存塊”不是所有剩余內(nèi)存塊之和,因?yàn)閙alloc 函數(shù)申請(qǐng)的是連續(xù)的一塊內(nèi)存。既然malloc 函數(shù)申請(qǐng)內(nèi)存有不成功的可能,那我們?cè)谑褂弥赶蜻@塊內(nèi)存的指針時(shí),必須用if (NULL !=p )語(yǔ)句來(lái)驗(yàn)證內(nèi)存確實(shí)分配成功了。3、用malloc 函數(shù)申請(qǐng)0字節(jié)內(nèi)存另外還有一個(gè)問(wèn)題:用malloc 函數(shù)申請(qǐng)0字節(jié)內(nèi)存會(huì)返回NULL 指針嗎?可以測(cè)試一下,也可以去查找關(guān)于malloc 函數(shù)的說(shuō)明文檔。申請(qǐng)0字節(jié)內(nèi)存,函數(shù)并不返回NULL ,而是返回一個(gè)正常的內(nèi)存地址。但是你卻無(wú)法使用這塊大小為0的內(nèi)存。這好尺子上的某個(gè)刻度,刻度本身并沒(méi)有長(zhǎng)度,只有某兩個(gè)刻度一起

16、才能量出長(zhǎng)度。對(duì)于這一點(diǎn)一定要小心,因?yàn)檫@時(shí)候if (NULL !=p )語(yǔ)句校驗(yàn)將不起作用。4、內(nèi)存釋放既然有分配,那就必須有釋放。不然的話(huà),有限的內(nèi)存總會(huì)用光,而沒(méi)有釋放的內(nèi)存卻在空閑。與malloc 對(duì)應(yīng)的就是free 函數(shù)了。free 函數(shù)只有一個(gè)參數(shù),就是所要釋放的內(nèi)存塊的首地址。比如上例:free(p;free 函數(shù)看上去挺狠的,但它到底作了什么呢?其實(shí)它就做了一件事:斬?cái)嘀羔樧兞颗c這塊內(nèi)存的關(guān)系。比如上面的例子,我們可以說(shuō)malloc 函數(shù)分配的內(nèi)存塊是屬于p 的,因?yàn)槲覀儗?duì)這塊內(nèi)存的訪(fǎng)問(wèn)都需要通過(guò)p 來(lái)進(jìn)行。free 函數(shù)就是把這塊內(nèi)存和p 之間的所有關(guān)系斬?cái)?。從此p 和那塊內(nèi)

17、存之間再無(wú)瓜葛。至于指針變量p 本身保存的地址并沒(méi)有改變,但是它對(duì)這個(gè)地址處的那塊內(nèi)存卻已經(jīng)沒(méi)有所有權(quán)了。那塊被釋放的內(nèi)存里面保存的值也沒(méi)有改變,只是再也沒(méi)有辦法使用了。這就是free 函數(shù)的功能。按照上面的分析,如果對(duì)p 連續(xù)兩次以上使用free 函數(shù),肯定會(huì)發(fā)生錯(cuò)誤。因?yàn)榈谝皇褂胒ree 函數(shù)時(shí),p 所屬的內(nèi)存已經(jīng)被釋放,第二次使用時(shí)已經(jīng)無(wú)內(nèi)存可釋放了。關(guān)于這點(diǎn),我上課時(shí)讓學(xué)生記住的是:一定要一夫一妻制,不然肯定出錯(cuò)。malloc 兩次只free 一次會(huì)內(nèi)存泄漏;malloc 一次free 兩次肯定會(huì)出錯(cuò)。也就是說(shuō),在程序中malloc 的使用次數(shù)一定要和free 相等,否則必有錯(cuò)誤。這種

18、錯(cuò)誤主要發(fā)生在循環(huán)使用malloc 函數(shù)時(shí),往往把malloc 和free 次數(shù)弄錯(cuò)了。這里留個(gè)練習(xí):寫(xiě)兩個(gè)函數(shù),一個(gè)生成鏈表,一個(gè)釋放鏈表。兩個(gè)函數(shù)的參數(shù)都只使用一個(gè)表頭指針。5、內(nèi)存釋放之后 既然使用 free 函數(shù)之后指針變量 p 本身保存的地址并沒(méi)有改變,那我們就需要重新把 p 的值變?yōu)?NULL: p = NULL; 這個(gè) NULL 就是我們前面所說(shuō)的“栓野狗的鏈子”。如果你不栓起來(lái)遲早會(huì)出問(wèn)題的。比如: 在 free(p)之后,你用 if(NULL != p)這樣的校驗(yàn)語(yǔ)句還能起作用嗎?例如: char *p = (char *malloc(100; strcpy(p, “hello”; free(p; /* p 所指的內(nèi)存被釋放,但是 p 所指的地址仍然不變*/ if (NULL != p /* 沒(méi)有起到防錯(cuò)作用*/ strcpy(p, “world”; /* 出錯(cuò)*/ 釋放完塊內(nèi)存之后,沒(méi)有把指針置 NULL,這個(gè)指針就成為了“野指針”,也有書(shū)叫“懸垂指 針”。這是很危險(xiǎn)的,而且也是經(jīng)常出錯(cuò)的地方。所以一定要記住一條:free 完之后,一定 要給指針置 NULL。 同時(shí)留一個(gè)問(wèn)題:對(duì) NULL 指針連續(xù) free 多次會(huì)出錯(cuò)嗎?為什么?如果讓你來(lái)設(shè)計(jì) free 函數(shù),你會(huì)怎么處理這個(gè)問(wèn)題? 六、內(nèi)存已經(jīng)被釋放了,但是繼續(xù)

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論