C/C++內(nèi)存泄漏原因分析與應(yīng)對方法
內(nèi)存泄漏
一、內(nèi)存泄漏的危害:
內(nèi)存泄漏會導(dǎo)致當(dāng)前應(yīng)用程序消耗更多的內(nèi)存,使得其他應(yīng)用程序可用的內(nèi)存更少了。
如果有個進(jìn)程可用的內(nèi)存不夠,就會觸發(fā)Linux操作系統(tǒng)的直接/后臺內(nèi)存回收(即將一些內(nèi)存頁的數(shù)據(jù)寫到磁盤里,那么該頁也就可用了,臟頁回寫)。雖然后臺回收是異步的不阻塞當(dāng)前進(jìn)程,但是內(nèi)存還是不夠會觸發(fā)直接內(nèi)存回收,最后內(nèi)存泄漏積累到一定程度,會直接觸發(fā)OOM,該機(jī)制會殺掉那些實(shí)時占用內(nèi)存大的進(jìn)程。
而且,即使沒有OOM,無論是直接回收還是后臺回收,都需要磁盤I/O而且需要多執(zhí)行額外的回收線程,使系統(tǒng)性能下降。
- 后臺內(nèi)存回收(kswapd):在物理內(nèi)存緊張的時候,會喚醒 kswapd 內(nèi)核線程來回收內(nèi)存,這個回收內(nèi)存的過程異步的,不會阻塞進(jìn)程的執(zhí)行。
- 直接內(nèi)存回收(direct reclaim):如果后臺異步回收跟不上進(jìn)程內(nèi)存申請的速度,就會開始直接回收,這個回收內(nèi)存的過程是同步的,會阻塞進(jìn)程的執(zhí)行,這個過程比較慢,導(dǎo)致CPU占用率飆升。
如果直接內(nèi)存回收后,空閑的物理內(nèi)存仍然無法滿足此次物理內(nèi)存的申請,那么內(nèi)核就會放最后的大招了 ——觸發(fā) OOM (Out of Memory)機(jī)制。
還有資源泄漏:
比如沒有關(guān)閉文件,程序提前return或報(bào)錯或忘記關(guān)閉,則可能導(dǎo)致想寫入文件的數(shù)據(jù)沒有真正落盤,從而丟失數(shù)據(jù)。
二、內(nèi)存泄漏舉例:
1,在free()前就返回了,或者是報(bào)錯并退出程序。要在程序的所有路徑上(if()的各個條件)都執(zhí)行資源釋放操作。
2,在析構(gòu)函數(shù)中未執(zhí)行內(nèi)存釋放操作。在構(gòu)造函數(shù)中申請了堆內(nèi)存或者打開了文件,在析構(gòu)函數(shù)中忘了釋放資源。
3,基類的析構(gòu)函數(shù)未聲明為虛函數(shù)。
析構(gòu)函數(shù)如果不聲明為虛函數(shù),可能會導(dǎo)致多態(tài)對象在刪除時無法正確調(diào)用派生類的析構(gòu)函數(shù)(如果子類構(gòu)造函數(shù)里malloc()了內(nèi)存,然后在析構(gòu)函數(shù)里free()),從而導(dǎo)致內(nèi)存泄漏。
4,shared_ptr循環(huán)引用導(dǎo)致內(nèi)存泄漏,用weak_ptr解決。如下示例:
class Contro { private: double* p; public: Contro() { p = new double[10]; } ~Controller() { delete[] p; std::cout << "in ~Contro" << std::endl; } // 類內(nèi)類 class SubContro { public: SubContro() { p = new double[10]; } ~SubContro() { delete[] p; std::cout << "in ~SubContro" << std::endl; } std::shared_ptr<Contro> controller_; }; std::shared_ptr<SubContro> sub_controller_; }; int main() { auto contro = std::make_shared<Contro>(); auto sub_contro = std::make_shared<Controller::SubContro>(); contro->sub_controller_ = sub_contro; sub_contro->controller_ = contro; // 打印引用計(jì)數(shù) std::cout << "controller use_count: " << contro.use_count() << std::endl; std::cout << "sub_controller use_count: " << sub_contro.use_count() << std::endl; return 0; }
發(fā)生循環(huán)引用,兩個的引用計(jì)數(shù)輸出都是2,所以main函數(shù)結(jié)束的時候,引用計(jì)數(shù)沒有減為0,就不會調(diào)用二者的析構(gòu)函數(shù),導(dǎo)致資源泄漏。
將SubContro類里的shared_ptr改成weak_ptr即可,后者不會增加引用計(jì)數(shù),因此兩個智能指針的引用計(jì)數(shù)都是1,然后main結(jié)束的時候,引用計(jì)數(shù)減少為0,然后執(zhí)行析構(gòu)函數(shù),此時不會發(fā)生內(nèi)存泄漏,輸出如下:
contro use_count: 1
sub_contro use_count: 2
in ~Contro
in ~SubContro
三、避免內(nèi)存泄漏的手段:
1. 靜態(tài)代碼檢查工具
(1)對于大型項(xiàng)目,可以使用啄木鳥這種靜態(tài)代碼分析工具
靜態(tài)代碼檢查工具會從詞法、語法、語義等多維度去對工程代碼掃描分析,發(fā)現(xiàn)可能存在的問題,比如變量未定義、類型不匹配、變量作用域問題、數(shù)組下標(biāo)越界、內(nèi)存泄露等問題。
既然是靜態(tài),那么就不是運(yùn)行時。但是是編譯前還是編譯后還是編譯中?
其實(shí)都有,啄木鳥是給源文件就行,然后它會在編譯的過程中去檢測語法,詞法,以及最后生成的二進(jìn)制。
代碼靜態(tài)分析(SAST):可以簡單的理解為在不執(zhí)行程序的情況下,對源代碼, 中間代碼或者二進(jìn)制代碼進(jìn)行分析的技術(shù)
(2)編譯成專門的內(nèi)存泄漏檢查版本。
可以把整個項(xiàng)目編譯成檢查內(nèi)存泄漏版本的可執(zhí)行文件,然后運(yùn)行相關(guān)工具,并且讓運(yùn)行結(jié)果專門記錄內(nèi)存泄漏,將泄漏結(jié)果放在對應(yīng)輸出文件上。
比如opengauss就有,參考鏈接:http://t.csdn.cn/DqusO
編譯openGauss時,編譯一個memcheck版的,然后通過跑fastcheck_single來發(fā)現(xiàn)代碼中的內(nèi)存問題。 編譯方式和編譯普通的openGauss基本一致,只是在configure時,添加一個 --enable-memory-check
參數(shù),編譯出來的就是memcheck版本的openGauss。
但是編譯前,要設(shè)置一些環(huán)境變量,ulimit -v unlimited
ulimit命令:用于控制shell程序的資源, -v <虛擬內(nèi)存大小>
指定可使用的虛擬內(nèi)存上限,單位為KB。
因?yàn)榭赡苡袃?nèi)存泄漏,所以就設(shè)置虛擬內(nèi)存大小為不受限制。
2. valgrind工具
可以安裝valgrind工具,指定工具--tool=memcheck
,也可以指定輸出日志,否則輸出在終端
--log-file=leak1.log
執(zhí)行命令如下:
valgrind --tool=memcheck ./a.out
如下可以看到malloc和free的次數(shù),以及被申請的字節(jié)數(shù):
HEAP SUMMARY: in use at exit: 8 bytes in 1 blocks total heap usage: 1 allocs, 0 frees, 8 bytes allocated
這個工具的用法還挺多,可以參考https://zhuanlan.zhihu.com/p/92074597
3. GDB調(diào)試
比如我們懷疑FUNC()
函數(shù)有內(nèi)存泄漏。
1,比如給某個函數(shù)FUNC()
打斷點(diǎn),進(jìn)入后這個函數(shù)里面也調(diào)用了很多其他函數(shù)func1,func2…,懷疑這些調(diào)用里面,或者外面有內(nèi)存泄漏。我們可以給malloc()和free打斷點(diǎn)(或者是自己封裝的函數(shù)),當(dāng)malloc()命中后,bt查看棧幀,就知道哪個函數(shù)調(diào)用了malloc,申請了堆內(nèi)存,比如func1,這樣可以重點(diǎn)關(guān)注該函數(shù)。
2,然后看后面free()
斷點(diǎn)有沒有命中,命中的時候查看棧幀,如果不是這個函數(shù)func1調(diào)用的free()
,那說明這個函數(shù)沒有執(zhí)行free。
3,此外,可以追蹤指針p的值(watch p
),看看它有沒有變?yōu)?x0,被釋放且被賦值為0x0,才不會成為懸空指針。
4,在函數(shù)FUNC()
的末尾,還可以看看malloc和free的斷點(diǎn)命中次數(shù),如果次數(shù)一樣,那沒問題。
以上就是C/C++內(nèi)存泄漏原因分析與應(yīng)對方法的詳細(xì)內(nèi)容,更多關(guān)于C/C++內(nèi)存泄漏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++使用CriticalSection實(shí)現(xiàn)線程同步實(shí)例
這篇文章主要介紹了C++使用CriticalSection實(shí)現(xiàn)線程同步實(shí)例,是使用CriticalSection對前文實(shí)例的擴(kuò)展,具有一定的參考借鑒價值,需要的朋友可以參考下2014-10-10C++ 復(fù)制控制之復(fù)制構(gòu)造函數(shù)的實(shí)現(xiàn)
所謂的“復(fù)制控制”即通過這三個成員函數(shù)控制對象復(fù)制的過程,本文主要介紹了C++ 復(fù)制控制之復(fù)制構(gòu)造函數(shù)的實(shí)現(xiàn),具有一定的參考價值,感興趣的可以了解一下2023-11-11Qt實(shí)現(xiàn)模糊匹配功能的實(shí)例詳解
對于瀏覽器的使用,我想大家一定不會陌生吧,輸入要搜索的內(nèi)容時,會出現(xiàn)相應(yīng)的匹配信息。本文就來用Qt實(shí)現(xiàn)模糊匹配功能,感興趣的可以了解一下2022-10-10Qt項(xiàng)目實(shí)戰(zhàn)之實(shí)現(xiàn)多文本編輯器
這篇文章主要為大家詳細(xì)介紹了如何利用Qt實(shí)現(xiàn)簡易的多文本編輯器,文中的示例代碼講解詳細(xì),具有一定的參考價值,感興趣的小伙伴可以了解一下2023-03-03C語言自定義類型超詳細(xì)梳理之結(jié)構(gòu)體 枚舉 聯(lián)合體
今天我們來學(xué)習(xí)一下自定義類型,自定義類型包括結(jié)構(gòu)體、枚舉、聯(lián)合體,小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考2022-03-03