C語言中的指針和內(nèi)存泄漏幾種情況

轉(zhuǎn)載导坟,詳見原文:https://blog.csdn.net/qq_32319583/article/details/53641469?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase

引言

原文地址:http://www.cnblogs.com/archimedes/p/c-point-memory-leak.html,轉(zhuǎn)載請(qǐng)注明源地址而姐。

對(duì)于任何使用C語言的人寄啼,如果問他們C語言的最大煩惱是什么愚争,其中許多人可能會(huì)回答說是指針和內(nèi)存泄漏。這些的確是消耗了開發(fā)人員大多數(shù)調(diào)試時(shí)間的事項(xiàng)链快。指針和內(nèi)存泄漏對(duì)某些開發(fā)人員來說似乎令人畏懼翩伪,但是一旦您了解了指針及其關(guān)聯(lián)內(nèi)存操作的基礎(chǔ)微猖,它們就是您在 C 語言中擁有的最強(qiáng)大工具。

本文將與您分享開發(fā)人員在開始使用指針來編程前應(yīng)該知道的秘密缘屹。本文內(nèi)容包括:

  • 導(dǎo)致內(nèi)存破壞的指針操作類型
  • 在使用動(dòng)態(tài)內(nèi)存分配時(shí)必須考慮的檢查點(diǎn)
  • 導(dǎo)致內(nèi)存泄漏的場景

如果您預(yù)先知道什么地方可能出錯(cuò)凛剥,那么您就能夠小心避免陷阱,并消除大多數(shù)與指針和內(nèi)存相關(guān)的問題轻姿。

什么地方可能出錯(cuò)犁珠?

有幾種問題場景可能會(huì)出現(xiàn),從而可能在完成生成后導(dǎo)致問題互亮。在處理指針時(shí)犁享,您可以使用本文中的信息來避免許多問題。

常見的內(nèi)存錯(cuò)誤及其對(duì)策如下:

1豹休、內(nèi)存分配未成功炊昆,卻使用了它

編程新手常犯這種錯(cuò)誤,因?yàn)樗麄儧]有意識(shí)到內(nèi)存分配會(huì)不成功威根。常用解決辦法是凤巨,在使用內(nèi)存之前檢查指針是否為NULL。如果指針p是函數(shù)的參數(shù)医窿,那么在函數(shù)

的入口處用assert(p!=NULL)進(jìn)行檢查磅甩。如果是用malloc或new來申請(qǐng)內(nèi)存,應(yīng)該用if(p==NULL) 或if(p!=NULL)進(jìn)行防錯(cuò)處理姥卢。

2卷要、內(nèi)存分配雖然成功,但是尚未初始化就引用它

犯這種錯(cuò)誤主要有兩個(gè)起因:一是沒有初始化的觀念独榴;二是誤以為內(nèi)存的缺省初值全為零僧叉,導(dǎo)致引用初值錯(cuò)誤(例如數(shù)組)。

內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn)棺榔,盡管有些時(shí)候?yàn)榱阒灯慷椋覀儗幙尚牌錈o不可信其有。所以無論用何種方式創(chuàng)建數(shù)組症歇,都別忘了賦初值郎笆,即便是賦零

值也不可省略,不要嫌麻煩。

3把敢、內(nèi)存分配成功并且已經(jīng)初始化洲押,但操作越過了內(nèi)存的邊界

例如在使用數(shù)組時(shí)經(jīng)常發(fā)生下標(biāo)“多1”或者“少1”的操作。特別是在for循環(huán)語句中凄吏,循環(huán)次數(shù)很容易搞錯(cuò),導(dǎo)致數(shù)組操作越界。

4痕钢、忘記了釋放內(nèi)存图柏,造成內(nèi)存泄露

含有這種錯(cuò)誤的函數(shù)每被調(diào)用一次就丟失一塊內(nèi)存。剛開始時(shí)系統(tǒng)的內(nèi)存充足任连,你看不到錯(cuò)誤蚤吹。終有一次程序突然死掉,系統(tǒng)出現(xiàn)提示:內(nèi)存耗盡随抠。

未初始化的內(nèi)存

在本例中距辆,p 已被分配了 10 個(gè)字節(jié)。這 10 個(gè)字節(jié)可能包含垃圾數(shù)據(jù)暮刃,如圖 1 所示跨算。

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">char *p = malloc ( 10 );</pre>

圖 1. 垃圾數(shù)據(jù)

image

如果在對(duì)這個(gè) p 賦值前,某個(gè)代碼段嘗試訪問它椭懊,則可能會(huì)獲得垃圾值诸蚕,您的程序可能具有不可預(yù)測(cè)的行為。p 可能具有您的程序從未曾預(yù)料到的值氧猬。

良好的實(shí)踐是始終結(jié)合使用 memsetmalloc背犯,或者使用 calloc

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">char *p = malloc (10);
memset(p,’\0’,10);</pre>

現(xiàn)在盅抚,即使同一個(gè)代碼段嘗試在對(duì) p 賦值前訪問它漠魏,該代碼段也能正確處理 Null 值(在理想情況下應(yīng)具有的值),然后將具有正確的行為妄均。

內(nèi)存覆蓋

由于 p 已被分配了 10 個(gè)字節(jié)柱锹,如果某個(gè)代碼片段嘗試向 p 寫入一個(gè) 11 字節(jié)的值,則該操作將在不告訴您的情況下自動(dòng)從其他某個(gè)位置“吃掉”一個(gè)字節(jié)丰包。讓我們假設(shè)指針 q 表示該內(nèi)存禁熏。

圖 2. 原始 q 內(nèi)容
image
圖 3. 覆蓋后的 q 內(nèi)容
image

結(jié)果,指針 q 將具有從未預(yù)料到的內(nèi)容邑彪。即使您的模塊編碼得足夠好瞧毙,也可能由于某個(gè)共存模塊執(zhí)行某些內(nèi)存操作而具有不正確的行為。下面的示例代碼片段也可以說明這種場景寄症。

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">char *name = (char *) malloc(11); // Assign some value to name
memcpy ( p,name,11); // Problem begins here</pre>

在本例中宙彪,memcpy 操作嘗試將 11 個(gè)字節(jié)寫到 p,而后者僅被分配了 10 個(gè)字節(jié)有巧。

作為良好的實(shí)踐释漆,每當(dāng)向指針寫入值時(shí),都要確保對(duì)可用字節(jié)數(shù)和所寫入的字節(jié)數(shù)進(jìn)行交叉核對(duì)剪决。一般情況下灵汪,memcpy 函數(shù)將是用于此目的的檢查點(diǎn)。

內(nèi)存讀取越界

內(nèi)存讀取越界 (overread) 是指所讀取的字節(jié)數(shù)多于它們應(yīng)有的字節(jié)數(shù)柑潦。這個(gè)問題并不太嚴(yán)重享言,在此就不再詳述了。下面的代碼提供了一個(gè)示例渗鬼。

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">char *ptr = (char *)malloc(10); char name[20] ;
memcpy ( name,ptr,20); // Problem begins here</pre>

在本例中览露,memcpy 操作嘗試從 ptr 讀取 20 個(gè)字節(jié),但是后者僅被分配了 10 個(gè)字節(jié)譬胎。這還會(huì)導(dǎo)致不希望的輸出差牛。

內(nèi)存泄漏

內(nèi)存泄漏可能真正令人討厭。下面的列表描述了一些導(dǎo)致內(nèi)存泄漏的場景堰乔。

  • 重新賦值

    我將使用一個(gè)示例來說明重新賦值問題偏化。

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">char *memoryArea = malloc(10); char *newArea = malloc(10);</pre>

這向如下面的圖 4 所示的內(nèi)存位置賦值。

圖 4. 內(nèi)存位置
image

memoryAreanewArea 分別被分配了 10 個(gè)字節(jié)镐侯,它們各自的內(nèi)容如圖 4 所示侦讨。如果某人執(zhí)行如下所示的語句(指針重新賦值)……

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">memoryArea = newArea;</pre>

則它肯定會(huì)在該模塊開發(fā)的后續(xù)階段給您帶來麻煩。

在上面的代碼語句中苟翻,開發(fā)人員將 memoryArea 指針賦值給 newArea 指針韵卤。結(jié)果,memoryArea 以前所指向的內(nèi)存位置變成了孤立的崇猫,如下面的圖 5 所示沈条。它無法釋放,因?yàn)闆]有指向該位置的引用诅炉。這會(huì)導(dǎo)致 10 個(gè)字節(jié)的內(nèi)存泄漏蜡歹。

圖 5. 內(nèi)存泄漏

image

在對(duì)指針賦值前,請(qǐng)確保內(nèi)存位置不會(huì)變?yōu)楣铝⒌摹?/p>

  • 首先釋放父塊

    假設(shè)有一個(gè)指針 memoryArea涕烧,它指向一個(gè) 10 字節(jié)的內(nèi)存位置季稳。該內(nèi)存位置的第三個(gè)字節(jié)又指向某個(gè)動(dòng)態(tài)分配的 10 字節(jié)的內(nèi)存位置,如圖 6所示澈魄。

image

如果通過調(diào)用 free 來釋放了 memoryArea景鼠,則 newArea 指針也會(huì)因此而變得無效。newArea 以前所指向的內(nèi)存位置無法釋放痹扇,因?yàn)橐呀?jīng)沒有指向該位置的指針铛漓。換句話說,newArea 所指向的內(nèi)存位置變?yōu)榱斯铝⒌啮旯梗瑥亩鴮?dǎo)致了內(nèi)存泄漏浓恶。

每當(dāng)釋放結(jié)構(gòu)化的元素,而該元素又包含指向動(dòng)態(tài)分配的內(nèi)存位置的指針時(shí)结笨,應(yīng)首先遍歷子內(nèi)存位置(在此例中為 newArea)包晰,并從那里開始釋放湿镀,然后再遍歷回父節(jié)點(diǎn)。

這里的正確實(shí)現(xiàn)應(yīng)該為:

<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">free( memoryArea->newArea);
free(memoryArea);</pre>

  • 返回值的不正確處理

    有時(shí)伐憾,某些函數(shù)會(huì)返回對(duì)動(dòng)態(tài)分配的內(nèi)存的引用勉痴。跟蹤該內(nèi)存位置并正確地處理它就成為了 calling 函數(shù)的職責(zé)。

    <pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; font-weight: normal; position: relative; font-family: Consolas, "Courier New", 宋體, Courier, mono, serif; white-space: pre-wrap; overflow-wrap: break-word; overflow-x: auto; font-size: 12px !important; line-height: 1; color: rgb(0, 0, 0);">char *func( )
    { return malloc(20); // make sure to memset this location to ‘\0’…
    } void callingFunc( )
    {
    func ( ); // Problem lies here
    }</pre>

    在上面的示例中树肃,callingFunc() 函數(shù)中對(duì) func() 函數(shù)的調(diào)用未處理該內(nèi)存位置的返回地址蒸矛。結(jié)果,func() 函數(shù)所分配的 20 個(gè)字節(jié)的塊就丟失了胸嘴,并導(dǎo)致了內(nèi)存泄漏雏掠。

歸還您所獲得的

在開發(fā)組件時(shí),可能存在大量的動(dòng)態(tài)內(nèi)存分配劣像。您可能會(huì)忘了跟蹤所有指針(指向這些內(nèi)存位置)乡话,并且某些內(nèi)存段沒有釋放,還保持分配給該程序耳奕。

始終要跟蹤所有內(nèi)存分配蚊伞,并在任何適當(dāng)?shù)臅r(shí)候釋放它們。事實(shí)上吮铭,可以開發(fā)某種機(jī)制來跟蹤這些分配时迫,比如在鏈表節(jié)點(diǎn)本身中保留一個(gè)計(jì)數(shù)器(但您還必須考慮該機(jī)制的額外開銷)。

訪問空指針

訪問空指針是非常危險(xiǎn)的谓晌,因?yàn)樗赡苁鼓某绦虮罎⒙尤J冀K要確保您不是 在訪問空指針。

總結(jié)

本文討論了幾種在使用動(dòng)態(tài)內(nèi)存分配時(shí)可以避免的陷阱纸肉。要避免內(nèi)存相關(guān)的問題溺欧,良好的實(shí)踐是:

  • 始終結(jié)合使用 memset 和 malloc,或始終使用 calloc柏肪。
  • 每當(dāng)向指針寫入值時(shí)姐刁,都要確保對(duì)可用字節(jié)數(shù)和所寫入的字節(jié)數(shù)進(jìn)行交叉核對(duì)。
  • 在對(duì)指針賦值前烦味,要確保沒有內(nèi)存位置會(huì)變?yōu)楣铝⒌摹?/li>
  • 每當(dāng)釋放結(jié)構(gòu)化的元素(而該元素又包含指向動(dòng)態(tài)分配的內(nèi)存位置的指針)時(shí)聂使,都應(yīng)首先遍歷子內(nèi)存位置并從那里開始釋放,然后再遍歷回父節(jié)點(diǎn)谬俄。
  • 始終正確處理返回動(dòng)態(tài)分配的內(nèi)存引用的函數(shù)返回值柏靶。
  • 每個(gè) malloc 都要有一個(gè)對(duì)應(yīng)的 free。
  • 確保您不是在訪問空指針溃论。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屎蜓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钥勋,更是在濱河造成了極大的恐慌炬转,老刑警劉巖辆苔,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扼劈,居然都是意外死亡驻啤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門测僵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谢翎,你說我怎么就攤上這事捍靠。” “怎么了森逮?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵榨婆,是天一觀的道長。 經(jīng)常有香客問我褒侧,道長良风,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任闷供,我火速辦了婚禮烟央,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歪脏。我一直安慰自己疑俭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布婿失。 她就那樣靜靜地躺著钞艇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豪硅。 梳的紋絲不亂的頭發(fā)上哩照,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音懒浮,去河邊找鬼飘弧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛砚著,可吹牛的內(nèi)容都是我干的眯牧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赖草,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼学少!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秧骑,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤版确,失蹤者是張志新(化名)和其女友劉穎扣囊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绒疗,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡侵歇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吓蘑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惕虑。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖磨镶,靈堂內(nèi)的尸體忽然破棺而出溃蔫,到底是詐尸還是另有隱情,我是刑警寧澤琳猫,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布伟叛,位于F島的核電站,受9級(jí)特大地震影響脐嫂,放射性物質(zhì)發(fā)生泄漏统刮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一账千、第九天 我趴在偏房一處隱蔽的房頂上張望侥蒙。 院中可真熱鬧,春花似錦匀奏、人聲如沸辉哥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽醋旦。三九已至,卻和暖如春会放,著一層夾襖步出監(jiān)牢的瞬間饲齐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工咧最, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捂人,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓矢沿,卻偏偏與公主長得像滥搭,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捣鲸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容