自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何通過(guò)wrap malloc定位C/C++程序的內(nèi)存泄漏

開(kāi)發(fā) 后端
用C/C++開(kāi)發(fā)的程序執(zhí)行效率很高,但卻經(jīng)常受到內(nèi)存泄漏的困擾。本文提供一種通過(guò)wrap malloc查找memory leak的思路。

用C/C++開(kāi)發(fā)的程序執(zhí)行效率很高,但卻經(jīng)常受到內(nèi)存泄漏的困擾。本文提供一種通過(guò)wrap malloc查找memory leak的思路。

[[278147]]

什么是內(nèi)存泄漏?

動(dòng)態(tài)申請(qǐng)的內(nèi)存丟失引用,造成沒(méi)有辦法回收它(我知道杠jing要說(shuō)進(jìn)程退出前系統(tǒng)會(huì)統(tǒng)一回收),這便是內(nèi)存泄漏。

Java等編程語(yǔ)言會(huì)自動(dòng)管理內(nèi)存回收,而C/C++需要顯式的釋放,有很多手段可以避免內(nèi)存泄漏,比如RAII,比如智能指針(大多基于引用計(jì)數(shù)計(jì)數(shù)),比如內(nèi)存池。

理論上,只要我們足夠小心,在每次申請(qǐng)的時(shí)候,都牢記釋放,那這個(gè)世界就清凈了,但現(xiàn)實(shí)往往沒(méi)有那么美好,比如拋異常了,釋放內(nèi)存的語(yǔ)句執(zhí)行不到,又或者某菜鳥(niǎo)程序員不小心埋了一個(gè)雷,所以,我們必須直面真實(shí)的世界,那就是我們會(huì)遭遇內(nèi)存泄漏。

怎么查內(nèi)存泄漏?

我們可以review代碼,但從海量代碼里找到隱藏的問(wèn)題,這如同大海撈針,往往兩手空空。

所以,我們需要借助工具,比如valgrind,但這些找內(nèi)存泄漏的工具,往往對(duì)你使用動(dòng)態(tài)內(nèi)存的方式有某種期待,或者說(shuō)約束,比如常駐內(nèi)存的對(duì)象會(huì)被誤報(bào)出來(lái),然后真正有用的信息會(huì)掩蓋在誤報(bào)的汪洋大海里。很多時(shí)候,甚至valgrind根本解決不了日常項(xiàng)目中的問(wèn)題。

所以很多著名的開(kāi)源項(xiàng)目,為了能用valgrind跑,都費(fèi)大力氣,大幅修改源代碼,從而使得項(xiàng)目符合valgrind的要求,滿足這些要求,用vargrind跑完沒(méi)有任何報(bào)警的項(xiàng)目叫valgrind干凈。

既然這些玩意兒都中看不中用,所以,求人不如求己,還是得自力更生。

什么是動(dòng)態(tài)內(nèi)存分配器?

動(dòng)態(tài)內(nèi)存分配器是介于kernel跟應(yīng)用程序之間的一個(gè)函數(shù)庫(kù),glibc提供的動(dòng)態(tài)內(nèi)存分配器叫ptmalloc,它也是應(yīng)用最廣泛的動(dòng)態(tài)內(nèi)存分配器實(shí)現(xiàn)。

從kernel角度看,動(dòng)態(tài)內(nèi)存分配器屬于應(yīng)用程序?qū)?而從應(yīng)用程序的角度看,動(dòng)態(tài)內(nèi)存分配器屬于系統(tǒng)層。

應(yīng)用程序可以通過(guò)mmap系統(tǒng)直接向kernel申請(qǐng)動(dòng)態(tài)內(nèi)存,也可以通過(guò)動(dòng)態(tài)內(nèi)存分配器的malloc接口分配內(nèi)存,而動(dòng)態(tài)內(nèi)存分配器會(huì)通過(guò)sbrk、mmap向kernel分配內(nèi)存,所以應(yīng)用程序通過(guò)free釋放的內(nèi)存,并不一定會(huì)真正返還給系統(tǒng),它也有可能被動(dòng)態(tài)內(nèi)存分配器緩存起來(lái)。

google有自己的動(dòng)態(tài)內(nèi)存分配器tcmalloc,另外jemalloc也是著名的動(dòng)態(tài)內(nèi)存分配器,他們有不同的性能表現(xiàn),也有不同的緩存和分配策略。你可以用它們替換linux系統(tǒng)glibc自帶的ptmalloc。

new/delete跟malloc/free的關(guān)系

new是c++的用法,比如Foo *f = new Foo,其實(shí)它分為3步。

  • 通過(guò)operator new()分配sizeof(Foo)的內(nèi)存,最終通過(guò)malloc分配。
  • 在新分配的內(nèi)存上構(gòu)建Foo對(duì)象。
  • 返回新構(gòu)建的對(duì)象地址。

new=分配內(nèi)存+構(gòu)造+返回,而delete則是等于析構(gòu)+free。

所以搞定malloc、free就是從根本上搞定動(dòng)態(tài)內(nèi)存分配。

1. chunk

每次通過(guò)malloc返回的一塊內(nèi)存叫一個(gè)chunk,動(dòng)態(tài)內(nèi)存分配器是這樣定義的,后面我們都這樣稱(chēng)呼。

2. wrap malloc

gcc支持wrap,即通過(guò)傳遞-Wl,--wrap,malloc的方式,可以改變調(diào)用malloc的行為,把對(duì)malloc的調(diào)用鏈接到自定義的__wrap_malloc(size_t)函數(shù),而我們可以在__wrap_malloc(size_t)函數(shù)的實(shí)現(xiàn)中通過(guò)__real_malloc(size_t)真正分配內(nèi)存,而后我們可以做搞點(diǎn)小動(dòng)作。

同樣,我們可以wrap free。malloc跟free是配對(duì)的,當(dāng)然也有其他相關(guān)API,比如calloc、realloc、valloc,但這根本上還是malloc+free,比如realloc就是malloc + free。

怎么去定位內(nèi)存泄漏呢?

我們會(huì)malloc各種不同size的chunk,也就是每種不同size的chunk會(huì)有不同數(shù)量,如果我們能夠跟蹤每種size的chunk數(shù)量,那就可以知道哪種size的chunk在泄漏。很簡(jiǎn)單,如果該size的chunk數(shù)量一直在增長(zhǎng),那它很可能泄漏。

光知道某種size的chunk泄漏了還不夠,我們得知道是哪個(gè)調(diào)用路徑上導(dǎo)致該size的chunk被分配,從而去檢查是不是正確釋放了。

怎么跟蹤到每種size的chunk數(shù)量?

我們可以維護(hù)一個(gè)全局 unsigned int malloc_map[1024 * 1024]數(shù)組,該數(shù)組的下標(biāo)就是chunk的size,malloc_map[size]的值就對(duì)應(yīng)到該size的chunk分配量。

這等于維護(hù)了一個(gè)chunk size到chunk count的映射表,它足夠快,而且它可以覆蓋到0 ~ 1M大小的chunk的范圍,它已經(jīng)足夠大了,試想一次分配一兆的塊已經(jīng)很恐怖了,可以覆蓋到大部分場(chǎng)景。

那大于1M的塊怎么辦呢?我們可以通過(guò)log記錄下來(lái)。

  • 在__wrap_malloc里,++malloc_map[size]
  • 在__wrap_free里,--malloc_map[size]

很簡(jiǎn)單,我們通過(guò)malloc_map記錄了各size的chunk的分配量。

如何知道釋放的chunk的size?

不對(duì),free(void *p)只有一個(gè)參數(shù),我如何知道釋放的chunk的size呢?怎么辦?

我們通過(guò)在__wrap_malloc(size_t)的時(shí)候,分配8+size的chunk,也就是多分配8字節(jié),開(kāi)始的8字節(jié)存儲(chǔ)該chunk的size,然后返回的是(char*)chunk + 8,也就是偏移8個(gè)字節(jié)返回給調(diào)用malloc的應(yīng)用程序。

這樣在free的時(shí)候,傳入?yún)?shù)void* p,我們把p往前移動(dòng)8個(gè)字節(jié),解引用就能得到該chunk的大小,而該大小值就是前一步,在__wrap_malloc的時(shí)候設(shè)置的size。

好了,我們真正做到記錄各size的chunk數(shù)量了,它就存在于malloc_map[1M]的數(shù)組中,假設(shè)64個(gè)字節(jié)的chunk一直在被分配,數(shù)量一直在增長(zhǎng),我們覺(jué)得該size的chunk很有可能泄漏,那怎么定位到是哪里調(diào)用過(guò)來(lái)的呢?

如何記錄調(diào)用鏈?

我們可以維護(hù)一個(gè)toplist數(shù)組,該數(shù)組假設(shè)有10個(gè)元素,它保存的是chunk數(shù)最大的10種size,這個(gè)很容易做到,通過(guò)對(duì)malloc_map取top 10就行。

然后我們?cè)赺_wrap_malloc(size_t)里,測(cè)試該size是不是toplist之一,如果是的話,那我們通過(guò)glibc的backtrace把調(diào)用堆棧dump到log文件里去。

注意:這里不能再分配內(nèi)存,所以你只能使用backtrace,而不能使用backtrace_symbols,這樣你只能得到調(diào)用堆棧的符號(hào)地址,而不是符號(hào)名。

如何把符號(hào)地址轉(zhuǎn)換成符號(hào)名,也就是對(duì)應(yīng)到代碼行呢?

addr2line

addr2line工具可以做到,你可以追查到調(diào)用鏈,進(jìn)而定位到內(nèi)存泄漏的問(wèn)題。

至此,你已經(jīng)get到了整個(gè)核心思想。

當(dāng)然,實(shí)際項(xiàng)目中,我們做的更多,我們不僅僅記錄了toplist size,還記錄了各size chunk的增量toplist,會(huì)記錄大塊的malloc/free,會(huì)wrap更多的API。

總結(jié)一下:通過(guò)wrap malloc/free + backtrace + addr2line,你就可以定位到內(nèi)存泄漏了。

 

責(zé)任編輯:趙寧寧 來(lái)源: 碼磚雜役
相關(guān)推薦

2011-06-16 09:28:02

C++內(nèi)存泄漏

2024-12-19 14:42:15

C++內(nèi)存泄漏內(nèi)存管理

2015-04-17 10:35:51

c++c++程序內(nèi)存泄漏檢測(cè)代碼

2010-01-28 10:33:10

C++開(kāi)發(fā)程序

2020-11-02 09:48:35

C++泄漏代碼

2010-01-25 18:15:52

2010-01-25 14:56:08

C++程序

2023-12-27 13:55:00

C++內(nèi)存分配機(jī)制new

2010-01-26 15:51:06

C++變量

2021-11-08 12:44:48

AndroidC++內(nèi)存

2020-05-26 13:25:00

語(yǔ)言編譯代碼

2011-04-11 09:47:50

C++內(nèi)存管理

2024-05-06 11:19:20

內(nèi)存池計(jì)算機(jī)編程

2024-01-09 09:23:12

指針C++

2010-01-26 17:27:58

C++C程序

2011-05-13 18:10:55

CC++

2023-11-17 11:40:51

C++內(nèi)存

2021-10-11 11:53:07

C++接口代碼

2021-02-26 10:41:59

C++程序員代碼

2019-05-24 16:20:11

Python 開(kāi)發(fā)編程語(yǔ)言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)