使用valgrind检测ATS插件中的内存泄露
一.內(nèi)存錯(cuò)誤出現(xiàn)的場(chǎng)景
這幾天在重構(gòu)ATS插件代碼的過(guò)程中遇到了煩人的內(nèi)存泄露問(wèn)題, 周五周六連續(xù)兩天通過(guò)走查代碼的方法,未能看出明顯的導(dǎo)致內(nèi)存錯(cuò)誤的代碼, 同時(shí)也覺(jué)得C和C++混合編程得到一個(gè)動(dòng)態(tài)庫(kù), 在一個(gè).cpp主文件中,即用new又用malloc來(lái)動(dòng)態(tài)分配內(nèi)存, 可能會(huì)導(dǎo)致內(nèi)存錯(cuò)誤.后來(lái)網(wǎng)上調(diào)研和查資料發(fā)現(xiàn), new和malloc混用還是允許的,因?yàn)镃++兼容C.雖然這種混用方式不提倡, 但絕對(duì)不會(huì)導(dǎo)致內(nèi)存堆棧錯(cuò)亂的情況.
這里之所以采用C和C++混合編程,是因?yàn)闃I(yè)務(wù)需要. ATS插件目前設(shè)計(jì)為一個(gè)transform主框架, 外加若干子模塊, 一個(gè)子模塊一個(gè)動(dòng)態(tài)庫(kù),后者可以根據(jù)不同業(yè)務(wù)需要進(jìn)行擴(kuò)展,而transform類(lèi)型的插件框架一般不變, 它只完成通常的transform變換流程就可以了. 這個(gè)主框架是使用C++開(kāi)發(fā)的, 它提供了業(yè)務(wù)實(shí)現(xiàn)接口, 供每個(gè)業(yè)務(wù)動(dòng)態(tài)庫(kù)實(shí)現(xiàn).因?yàn)榻涌谌雲(yún)⒅杏玫搅藄tl的map結(jié)構(gòu)來(lái)在主框架和子模塊之間傳遞參數(shù),所以接口的實(shí)現(xiàn)必須是C++方式的. 但是我的一個(gè)業(yè)務(wù)插件原來(lái)是使用純c編寫(xiě)的, 引用了幾個(gè)第三方庫(kù),比如JSON, libz, pcre庫(kù)等, 它們?cè)瓉?lái)已經(jīng)用C編寫(xiě)好了,并且經(jīng)過(guò)測(cè)試發(fā)現(xiàn)是可靠的, 如果再換為C++實(shí)現(xiàn)一般不太現(xiàn)實(shí), 也沒(méi)有必要. 這樣造成的局面就是, 目前只能C和C++進(jìn)行混合編程, 對(duì)C提供的接口, 要在h文件中使用 extern "C"聲明, 并將實(shí)現(xiàn)存放在對(duì)應(yīng)的c文件中.(這里必須要分離成兩個(gè)文件, 如果以前只有一個(gè)h文件就搞定的, 現(xiàn)在要分為h和c文件).在編譯出一個(gè)so時(shí), 同時(shí)要外鏈其他的第三方基礎(chǔ)庫(kù)時(shí), Makefile的編寫(xiě)通常有些技巧的, 請(qǐng)參見(jiàn)我的博文
C和C++混合編程的Makefile的編寫(xiě)!
這種情況下, 對(duì)C那部分的調(diào)用,我采用malloc和free來(lái)分配和釋放內(nèi)存, 對(duì)上層接口那部分, 我在主cpp文件中使用new和delete來(lái)分配內(nèi)存.這里明顯使得內(nèi)存的使用更加復(fù)雜化了,但是不管怎樣,按理說(shuō)這種做法還是行得通的.
但是在調(diào)試過(guò)程中,我卻發(fā)現(xiàn)了一個(gè)內(nèi)存越界的錯(cuò)誤, 每次提示的錯(cuò)誤都不一樣, 但是顯示出錯(cuò)的位置又沒(méi)有明顯的錯(cuò)誤, 真是讓人摸不到頭腦, 搞不定了, 郁悶, 失敗至極.....
二.內(nèi)存錯(cuò)誤出現(xiàn)的表象
兩天的搜索無(wú)果, 讓我只能尋求內(nèi)存檢測(cè)軟件的幫助. 我以前用過(guò)tcmalloc, 就是google的gperftools里面的一個(gè)工具,它會(huì)將系統(tǒng)默認(rèn)的內(nèi)存分配釋放函數(shù)替換為自己的函數(shù)再進(jìn)行檢測(cè),但是在這里不方便施展, 因?yàn)檫@里new和malloc都用上了, 所以我決定使用valgrind來(lái)檢測(cè). 具體安裝使用方法,請(qǐng)參見(jiàn)
在Ubuntu 14.04 64bit上安裝Valgrind并檢查內(nèi)存泄露
下面是ATS插件運(yùn)行中崩潰時(shí)valgrind檢測(cè)的幾個(gè)錯(cuò)誤截圖
該截圖告訴我們, pool_alloc(在mem_manage.c第17行)調(diào)用malloc分配了1813字節(jié)內(nèi)存, find_replace_html(位于main.cpp第484行)函數(shù)使用memcpy從1808處開(kāi)始要復(fù)制8個(gè)字節(jié), 而事實(shí)上只能復(fù)制4字節(jié),這會(huì)內(nèi)存越界,導(dǎo)致非法的8字節(jié)寫(xiě)入(越界寫(xiě)入).
該截圖告訴我們,??pool_alloc(在mem_manage.c第17行)調(diào)用malloc分配了1813字節(jié)內(nèi)存,pcre_exec(在?regex_lookup.h第45行)在1813字節(jié)后面越界讀取了1個(gè)字節(jié).
該截圖告訴我們, 在分配的1813字節(jié)內(nèi)部的1808字節(jié)處, 要非法讀取8字節(jié)內(nèi)存(越界讀取)
三.內(nèi)存錯(cuò)誤的分析和確定
上面的截圖分析, 很明確地傳遞出內(nèi)存越界讀取和越界寫(xiě)入的錯(cuò)誤, 但是我查看內(nèi)存分配的地方, 沒(méi)有看到明顯的錯(cuò)誤.但是多次調(diào)試顯示的內(nèi)存錯(cuò)誤大多顯示到memset這樣的地方, 將部分memset代碼注釋掉后, 程序能運(yùn)行, 但是運(yùn)行一段時(shí)間后,它還是會(huì)有段錯(cuò)誤, 看來(lái)找到的位置不對(duì), 而且上面截圖顯示的都是造成錯(cuò)誤的表現(xiàn), 不是真正造成內(nèi)存出錯(cuò)的地方.最后經(jīng)過(guò)認(rèn)真理解valgrind的錯(cuò)誤提示, 和仔細(xì)地分析代碼, 終于定位到內(nèi)存錯(cuò)誤的真正地方不是malloc長(zhǎng)度那里, 而是memset那里, 初始化內(nèi)存的那個(gè)指針只能是一個(gè)void*或char*, 不能是一個(gè)結(jié)構(gòu)體, 這就是內(nèi)存錯(cuò)誤的真正地方.
參見(jiàn)下面的截圖, 有內(nèi)存錯(cuò)誤的源碼截圖
改正內(nèi)存錯(cuò)誤的源碼截圖
改正后, 重新編譯調(diào)試, 一切正常, 運(yùn)行很長(zhǎng)時(shí)間, 都沒(méi)有再崩掉, 再次印證修改是正確的.而這個(gè)錯(cuò)誤, 是一個(gè)大家看起來(lái)很可笑的問(wèn)題, 所要大家編寫(xiě)代碼時(shí)還是要提高修養(yǎng)和注意力,千萬(wàn)不要馬虎寫(xiě)代碼,否則會(huì)害死自己的.
四.回顧反思
bug不可怕, 犯錯(cuò)不可恥, 但是仔細(xì)分析導(dǎo)致bug的原因, 吸取教訓(xùn)并避免再犯才是最重要的. 另外一點(diǎn)是, 排錯(cuò)的鍛煉和快速定位是一項(xiàng)職業(yè)素養(yǎng), 越修bug越有收獲越能提高, 經(jīng)歷越豐富自信心越強(qiáng), 所以bug來(lái)了,我們還是得從容面對(duì).
總結(jié)
以上是生活随笔為你收集整理的使用valgrind检测ATS插件中的内存泄露的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 在Ubuntu 14.04 64bit上
- 下一篇: Ubuntu 14.04 64bit上升