內(nèi)存泄漏

內(nèi)存問題的可能情況

內(nèi)存泄漏(memory leak)指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存谦炒。內(nèi)存泄漏并非指內(nèi)存在物理上的消失眶根,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計(jì)錯(cuò)誤竭缝,導(dǎo)致在釋放該段內(nèi)存之前就失去了對(duì)該段內(nèi)存的控制希太,從而造成了內(nèi)存的浪費(fèi)。這樣一直的內(nèi)存泄漏下去琅锻,最后會(huì)導(dǎo)致機(jī)器的內(nèi)存不足卦停。在最糟糕的情況下向胡,過多的可用內(nèi)存被分配掉導(dǎo)致全部或部分設(shè)備停止正常工作,或者應(yīng)用程序崩潰惊完。

內(nèi)存溢出(out of memory)是指程序在申請(qǐng)內(nèi)存時(shí)僵芹,沒有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory小槐;比如申請(qǐng)了一個(gè)integer,但給它存了long才能存下的數(shù)拇派,那就是內(nèi)存溢出。不同的編程語言凿跳,對(duì)內(nèi)存溢出的處理也不一樣件豌。在某些情況下,可能直接導(dǎo)致程序異晨厥龋或者崩潰茧彤。在另外一些情況下,可能是程序無法正常運(yùn)行疆栏,或者性能下降曾掂。

內(nèi)存越界 是指向系統(tǒng)申請(qǐng)一塊內(nèi)存后,使用時(shí)卻超出申請(qǐng)范圍壁顶。比如一些操作內(nèi)存的函數(shù):sprintf珠洗、strcpy、strcat若专、vsprintf许蓖、memcpy、memset调衰、memmove蛔糯。當(dāng)造成內(nèi)存泄漏的代碼運(yùn)行時(shí),所帶來的錯(cuò)誤是無法避免的窖式,通常會(huì)造成
1.破壞了堆中內(nèi)存內(nèi)存分配信息數(shù)據(jù)
2.破壞了程序其他對(duì)象的內(nèi)存空間
3.破壞了空閑內(nèi)存塊

緩沖區(qū)溢出(棧溢出)指程序?yàn)榱伺R時(shí)存取數(shù)據(jù)的需要,一般會(huì)分配一些內(nèi)存空間稱為緩沖區(qū)动壤。如果向緩沖區(qū)中寫入緩沖區(qū)無法容納的數(shù)據(jù)萝喘,機(jī)會(huì)造成緩沖區(qū)以外的存儲(chǔ)單元被改寫,稱為緩沖區(qū)溢出琼懊。而棧溢出是緩沖區(qū)溢出的一種阁簸,原理也是相同的。分為上溢出和下溢出。其中,上溢出是指棧滿而又向其增加新的數(shù)據(jù)粗卜,導(dǎo)致數(shù)據(jù)溢出撤嫩;下溢出是指空棧而又進(jìn)行刪除操作等,導(dǎo)致空間溢出增炭。

內(nèi)存泄漏分類

  1. 常發(fā)性內(nèi)存泄漏踩蔚。發(fā)生內(nèi)存泄漏的代碼會(huì)被多次執(zhí)行到砚嘴,每次被執(zhí)行的時(shí)候都會(huì)導(dǎo)致一塊內(nèi)存泄漏檬输。
  2. 偶發(fā)性內(nèi)存泄漏照瘾。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過程下才會(huì)發(fā)生。常發(fā)性和偶發(fā)性是相對(duì)的丧慈。對(duì)于特定的環(huán)境析命,偶發(fā)性的也許就變成了常發(fā)性的。所以測試環(huán)境和測試方法對(duì)檢測內(nèi)存泄漏至關(guān)重要逃默。
  3. 一次性內(nèi)存泄漏鹃愤。發(fā)生內(nèi)存泄漏的代碼只會(huì)被執(zhí)行一次,或者由于算法上的缺陷完域,導(dǎo)致總會(huì)有一塊僅且一塊內(nèi)存發(fā)生泄漏软吐。比如,在類的構(gòu)造函數(shù)中分配內(nèi)存筒主,在析構(gòu)函數(shù)中卻沒有釋放該內(nèi)存关噪,所以內(nèi)存泄漏只會(huì)發(fā)生一次。
  4. 隱式內(nèi)存泄漏乌妙。程序在運(yùn)行過程中不停的分配內(nèi)存使兔,但是直到結(jié)束的時(shí)候才釋放內(nèi)存。嚴(yán)格的說這里并沒有發(fā)生內(nèi)存泄漏藤韵,因?yàn)樽罱K程序釋放了所有申請(qǐng)的內(nèi)存虐沥。但是對(duì)于一個(gè)服務(wù)器程序,需要運(yùn)行幾天泽艘,幾周甚至幾個(gè)月欲险,不及時(shí)釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以匹涮,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏天试。

用戶感知

從有計(jì)算機(jī)開始,就有內(nèi)存的的存在然低,使用好內(nèi)存對(duì)成為一個(gè)優(yōu)秀的軟件工程師至關(guān)重要喜每。

但是從用戶使用程序的角度來看,內(nèi)存泄漏本身不會(huì)產(chǎn)生什么危害雳攘,作為一般的用戶带兜,根本感覺不到內(nèi)存泄漏的存在。我們平時(shí)使用的PC或者M(jìn)AC吨灭,或者應(yīng)用程序即使存在內(nèi)存泄漏刚照,基本也不會(huì)有感知,普通用戶根本不會(huì)長時(shí)間去使用一個(gè)應(yīng)用程序喧兄。但是長時(shí)間的使用操作系統(tǒng)是非常有可能的无畔,在以前的Windows系統(tǒng)啊楚,經(jīng)常性的藍(lán)屏,問題多數(shù)就跟內(nèi)存的管理使用有關(guān)檩互,尤其是內(nèi)存泄漏的問題多特幔。

真正有危害的是內(nèi)存泄漏的堆積,這會(huì)最終消耗盡系統(tǒng)所有的內(nèi)存闸昨。從這個(gè)角度來說蚯斯,一次性內(nèi)存泄漏并沒有什么危害,因?yàn)樗粫?huì)堆積饵较,而隱式內(nèi)存泄漏危害性則非常大拍嵌,因?yàn)檩^之于常發(fā)性和偶發(fā)性內(nèi)存泄漏它更難被檢測到。

解決方案與預(yù)防

最簡單的內(nèi)存泄漏

如下的代碼:

#include <stdio.h>
#include <stdlib.h>

void foo() {
    char *p = (char*) malloc(10);
    p[0] = 'a';
    printf("%s \n", p);
}

int main(int argc, char** argv) {
    foo();
    return 0;
}

這段代碼看著沒什么問題循诉,申請(qǐng)的內(nèi)存沒有free掉横辆,但是程序退出的時(shí)候會(huì)自動(dòng)回收。確實(shí)是這樣子茄猫,但是如果調(diào)用foo()函數(shù)的是線程狈蚤,每一個(gè)線程都這樣只申請(qǐng)內(nèi)存不釋放內(nèi)存,那么每開一次新的線程就會(huì)申請(qǐng)一次內(nèi)存划纽,最后內(nèi)存就會(huì)不斷的攀升脆侮,最后吃完機(jī)器的內(nèi)存。這是非常的明顯的例子勇劣,這樣的錯(cuò)誤一般是不應(yīng)該發(fā)生的靖避,只需要由如下的編碼習(xí)慣:

  • malloc和free函數(shù)配對(duì)使用,一個(gè)malloc對(duì)應(yīng)一個(gè)free
  • new和delete或delete[]配對(duì)使用

那么申請(qǐng)的內(nèi)存就會(huì)釋放比默,可以避免掉這種簡單錯(cuò)誤幻捏。

C語言函數(shù)庫一些不安全的函數(shù)

如下代碼:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    char* dst = "hello";
    const char* src = "Hi world";
    char* p = strcpy(dst, src);
    printf("%s \n", p);
    printf("%s \n", dst);
    return 0;
}

這段代碼顯然是有問題的,編譯結(jié)果后命咐,運(yùn)行直接出錯(cuò)篡九,不會(huì)有任何結(jié)果。

C和C++不能夠自動(dòng)地做邊界檢查醋奠,邊界檢查的代價(jià)是效率瓮下,于是常常就出現(xiàn)了越界的行為。一般來講钝域,C 在大多數(shù)情況下注重效率。然而锭魔,獲得效率的代價(jià)是例证,C 程序員必須十分警覺以避免緩沖區(qū)溢出問題。而且這種行為往往是C語言未定義(Undefined Behavior)的行為迷捧,具體怎么處理织咧,還得看編譯器的臉色胀葱。

C語言標(biāo)準(zhǔn)庫中的許多字符串處理和IO流讀取函數(shù)是導(dǎo)致緩沖區(qū)溢出的罪魁禍?zhǔn)住N覀冇斜匾私膺@些函數(shù)笙蒙,在編程中多加小心抵屿。如下列出一些不安全的函數(shù):

strcpy()
strcat()
sprintf()、vsprintf() 
gets()
getchar()捅位、fgetc()轧葛、getc()、read()
scanf()艇搀、sscanf()尿扯、fscanf()、vfscanf()焰雕、vscanf()衷笋、vsscanf()
getenv()

以上的這些函數(shù)現(xiàn)在基本都有安全替代的版本,建議大家都使用安全替代版本矩屁。

內(nèi)存泄漏檢測工具

在一般的小型項(xiàng)目到還好辟宗,基本的問題都能在寫代碼的時(shí)候察覺。但是在大規(guī)模項(xiàng)目上吝秕,一個(gè)人要管理編寫的代碼可能上萬行泊脐,還要與同事分工合作,不太可能一行一行的代碼檢查郭膛。所以機(jī)智的前人們?cè)缇烷_發(fā)了一些好用的內(nèi)存檢測工具晨抡,雖然這些內(nèi)存檢測工具不能把所有的問題都檢測出來,但是常見的問題都是沒問題则剃。

valgrind內(nèi)存檢測工具集

Valgrind通常用來成分析程序性能及程序中的內(nèi)存泄露錯(cuò)誤耘柱,他是一套工具集。

1棍现、memcheck:檢查程序中的內(nèi)存問題调煎,如泄漏、越界己肮、非法指針等士袄。
2、callgrind:檢測程序代碼的運(yùn)行時(shí)間和調(diào)用過程谎僻,以及分析程序性能娄柳。
3、cachegrind:分析CPU的cache命中率艘绍、丟失率赤拒,用于進(jìn)行代碼優(yōu)化。
4、helgrind:用于檢查多線程程序的競態(tài)條件挎挖。
5这敬、massif:堆棧分析器,指示程序中使用了多少堆內(nèi)存等信息蕉朵。
6崔涂、lackey:
7、nulgrind:

這幾個(gè)工具的使用是通過命令:valgrind --tool=name 程序名來分別調(diào)用的始衅,當(dāng)不指定tool參數(shù)時(shí)默認(rèn)是 --tool=memcheck冷蚂。更多的詳細(xì)使用請(qǐng)參考手冊(cè)

對(duì)以上會(huì)出錯(cuò)的代碼做檢測:
valgrind --leak-check=full --show-reachable=yes --trace-children=yes ./main
結(jié)果如下:

==31754== Memcheck, a memory error detector
==31754== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==31754== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==31754== Command: ./main
==31754==
==31754==
==31754== Process terminating with default action of signal 11 (SIGSEGV)
==31754==  Bad permissions for mapped region at address 0x108784
==31754==    at 0x4C32E00: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==31754==    by 0x1086C1: main (in /home/xingyaoma/main)
==31754==
==31754== HEAP SUMMARY:
==31754==     in use at exit: 0 bytes in 0 blocks
==31754==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==31754==
==31754== All heap blocks were freed -- no leaks are possible
==31754==
==31754== For counts of detected and suppressed errors, rerun with: -v
==31754== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

結(jié)果會(huì)有許多的顯示觅闽,按照結(jié)果的提示帝雇,就可以在代碼中去找一些問題了。

在前面的示例中并沒有把所有可能內(nèi)存的問題都列出來蛉拙,只是其中的一些情況尸闸。在實(shí)際開發(fā)中,配合valgrind工具孕锄,我們可以開發(fā)出穩(wěn)定高效可靠的C/C++代碼吮廉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市畸肆,隨后出現(xiàn)的幾起案子宦芦,更是在濱河造成了極大的恐慌,老刑警劉巖轴脐,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件调卑,死亡現(xiàn)場離奇詭異,居然都是意外死亡大咱,警方通過查閱死者的電腦和手機(jī)恬涧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碴巾,“玉大人溯捆,你說我怎么就攤上這事∠闷埃” “怎么了提揍?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長煮仇。 經(jīng)常有香客問我劳跃,道長,這世上最難降的妖魔是什么浙垫? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任售碳,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贸人。我一直安慰自己,他們只是感情好佃声,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布艺智。 她就那樣靜靜地躺著,像睡著了一般圾亏。 火紅的嫁衣襯著肌膚如雪十拣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天志鹃,我揣著相機(jī)與錄音夭问,去河邊找鬼。 笑死曹铃,一個(gè)胖子當(dāng)著我的面吹牛缰趋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陕见,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼秘血,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了评甜?” 一聲冷哼從身側(cè)響起灰粮,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忍坷,沒想到半個(gè)月后粘舟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佩研,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年柑肴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片韧骗。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘉抒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出袍暴,到底是詐尸還是另有隱情些侍,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布政模,位于F島的核電站岗宣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淋样。R本人自食惡果不足惜耗式,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刊咳,春花似錦彪见、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至跷坝,卻和暖如春酵镜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柴钻。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工淮韭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贴届。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓靠粪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粱腻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子庇配,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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

  • 由于 C 和 C++ 程序中完全由程序員自主申請(qǐng)和釋放內(nèi)存,稍不注意绍些,就會(huì)在系統(tǒng)中導(dǎo)入內(nèi)存錯(cuò)誤捞慌。同時(shí),內(nèi)存錯(cuò)誤往往...
    帥氣滴點(diǎn)C閱讀 1,052評(píng)論 0 2
  • “該死系統(tǒng)存在內(nèi)存泄漏問題”柬批,項(xiàng)目中由于各方面因素啸澡,總是有人抱怨存在內(nèi)存泄漏,系統(tǒng)長時(shí)間運(yùn)行之后氮帐,可用內(nèi)存越來越少...
    7ee72f98ad17閱讀 317評(píng)論 0 1
  • 1.內(nèi)存泄漏 內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放嗅虏,造成...
    Sun灬2019閱讀 247評(píng)論 0 1
  • 內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。內(nèi)存泄漏大家都不陌生了上沐,簡單粗俗的講皮服,...
    宇宙只有巴掌大閱讀 2,361評(píng)論 0 12
  • 行者順達(dá) 歲歲重陽今重陽, 秋月晨曦晴空朗参咙。 舞劍佩帶隨風(fēng)旋龄广, 翁練健步影流暢。 揮劍向天起舞茫蕴侧, 斬?cái)嗌n穹我心狂...
    行者順達(dá)閱讀 236評(píng)論 0 0