Linux平台中調試C/C++內存泄漏方法 (騰訊和MTK面試的時候問到的)

2019年11月16日01:00:15 科技 1288

由於 C 和 C++ 程序中完全由程序員自主申請和釋放內存,稍不注意,就會在系統中導入內存錯誤。同時,內存錯誤往往非常嚴重,一般會帶來諸如系統崩潰,內存耗盡這樣嚴重的 後果。本文將從靜態分析和動態檢測兩個角度介紹在 Linux 環境進行內存泄漏檢測的方法,並重點介紹靜態分析工具 BEAM、動態監測工具 Valgrind 和 rational purify 的使用方法。相信通過本文的介紹,能給大家對處理其它產品或項目內存泄漏相關的問題時提供借鑒。

從 歷史上看,來自計算機應急響應小組和供應商的許多最嚴重的安全公告都是由簡單的內存錯誤造成的。自從 70 年代末期以來,C/C++ 程序員就一直討論此類錯誤,但其影響在 2007 年仍然很大。與許多其他類型的常見錯誤不同,內存錯誤通常具有隱蔽性,即它們很難再現,癥狀通常不能在相應的源代碼中找到。例如,無論何時何地發生內存泄 漏,都可能表現為應用程序完全無法接受,同時內存泄漏不是顯而易見[1]。存在內存錯誤的 C 和 C++ 程序會導致各種問題。如果它們泄漏內存,則運行速度會逐漸變慢,並最終停止運行;如果覆蓋內存,則會變得非常脆弱,很容易受到惡意用戶的攻擊。

因 此,出於這些原因,需要特別關注 C 和 C++ 編程的內存問題,特別是內存泄漏。本文先從如何發現內存泄漏,然後使用不同的方法和工具定位內存泄漏,最後對這些工具進行了比較,另外還簡單介紹了資源泄 漏的處理(以句柄泄漏為例)。本文使用的測試平台是:Linux (Redhat AS4)。但是這些方法和工具許多都不只是局限於 C/C++ 語言以及 linux 操作系統。

內存泄漏一般指的是堆內存的泄漏。堆內存是指程序從堆中分配的、大小任意的(內存塊的大小可以在程序 運行期決定)、使用完後必須顯示的釋放的內存。應用程序一般使用malloc、realloc、new 等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用 free 或 delete 釋放該內存塊。否則,這塊內存就不能被再次使用,我們就說這塊內存泄漏了。

1. 如何發現內存泄漏

有些 簡單的內存泄漏問題可以從在代碼的檢查階段確定。還有些泄漏比較嚴重的,即在很短的時間內導致程序或系統崩潰,或者系統報告沒有足夠內存,也比較容易發 現。最困難的就是泄漏比較緩慢,需要觀測幾天、幾周甚至幾個月才能看到明顯異常現象。那麼如何在比較短的時間內檢測出有沒有潛在的內存泄漏問題呢?實際上 不同的系統都帶有內存監視工具,我們可以從監視工具收集一段時間內的堆棧內存信息,觀測增長趨勢,來確定是否有內存泄漏。在 Linux 平台可以用 ps 命令,來監視內存的使用,比如下面的命令 (觀測指定進程的VSZ值):

ps -aux

2. 靜態分析

包括手動檢測和靜態工具分析,這是代價最小的調試方法。

2.1 手動檢測

當使用 C/C++ 進行開發時,採用良好的一致的編程規範是防止內存問題第一道也是最重要的措施。檢測是編碼標準的補充。二者各有裨益,但結合使用效果特別好。專業的 C 或 C++ 專業人員甚至可以瀏覽不熟悉的源代碼,並以極低的成本檢測內存問題。通過少量的實踐和適當的文本搜索,您能夠快速驗證平衡的 *alloc() 和 free() 或者 new 和 delete 的源主體。人工查看此類內容通常會出現像清單 1 中一樣的問題,可以定位出在函數 LeakTest 中的堆變量 Logmsg 沒有釋放。

清單1. 簡單的內存泄漏

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

int LeakTest(char * Para)

{

if(NULL==Para){

//local_log("LeakTest Func: empty parameter/n");

return -1;

}

char * Logmsg = new char[128];

if(NULL == Logmsg){

//local_log("memeory allocation failed/n");

return -2;

}

sprintf(Logmsg,"LeakTest routine exit: '%s'./n", Para);

//local_log(Logmsg);

return 0;

}

int main(int argc,char **argv )

{

char szInit [] = "testcase1";

LeakTest(szInit);

return 0;

}

2.2 靜態代碼分析工具

代碼靜態掃描和分析的工具比較多,比如 splint, PC-LINT, BEAM 等。因為 BEAM 支持的平台比較多,這以 BEAM 為例,做個簡單介紹,其它有類似的處理過程。

BEAM 可以檢測四類問題: 沒有初始化的變量;廢棄的空指針;內存泄漏;冗餘計算。而且支持的平台比較多。

BEAM 支持以下平台:

  • Linux x86 (glibc 2.2.4)
  • Linux s390/s390x (glibc 2.3.3 or higher)
  • Linux (PowerPC, USS) (glibc 2.3.2 or higher)
  • AIX (4.3.2+)
  • Window2000 以上

清單2. 用作 Beam 分析的代碼

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

int *p;

void

foo(int a)

{

int b, c;

b = 0;

if(!p) c = 1;

if(c > a)

c += p[1];

}

int LeakTest(char * Para)

{

char * Logmsg = new char[128];

if((Para==NULL)||(Logmsg == NULL))

return -1; sprintf(Logmsg,"LeakTest routine exit: '%s'./n", Para); return 0;

}

int main(int argc,char **argv )

{

char szInit [] = "testcase1";

LeakTest(szInit);

return 0;

}

下面以 X86 Linux 為例,代碼如清單 2,具體的環境如下:

OS: Red Hat Enterprise Linux AS release 4 (Nahant Update 2)

GCC: gcc version 3.4.4

BEAM: 3.4.2; https://w3.eda.ibm.com/beam/

可以把 BEAM 看作一個 C/C++ 編譯器,按下面的命令進行編譯 (前面兩個命令是設置編譯器環境變量):

./beam-3.4.2/bin/beam_configure --c gcc

./beam-3.4.2/bin/beam_configure --cpp g++

./beam-3.4.2/bin/beam_compile --beam::compiler=compiler_cpp_config.tcl -cpp code2.cpp

從下面的編譯報告中,我們可以看到這段程序中有三個錯誤:”內存泄漏”;“變量未初始化”;“ 空指針操作”

"code2.cpp", line 10: warning: variable "b" was set but never used

int b, c;

^

BEAM_VERSION=3.4.2

BEAM_ROOT=/home/hanzb/memdetect

BEAM_DIRECTORY_WRITE_INNOCENTS=

BEAM_DIRECTORY_WRITE_ERRORS=

-- ERROR23(heap_memory) /*memory leak*/ >>>ERROR23_LeakTest_7b00071dc5cbb458

"code2.cpp", line 24: memory leak

ONE POSSIBLE PATH LEADING TO THE ERROR:

"code2.cpp", line 22: allocating using `operator new[]' (this memory will not be freed)

"code2.cpp", line 22: assigning into `Logmsg'

"code2.cpp", line 24: deallocating `Logmsg' because exiting its scope (losing last pointer to the memory)

-- ERROR1 /*uninitialized*/ >>>ERROR1_foo_60c7889b2b608

"code2.cpp", line 16: uninitialized `c'

ONE POSSIBLE PATH LEADING TO THE ERROR:

"code2.cpp", line 10: allocating `c'

"code2.cpp", line 13: the if-condition is false

"code2.cpp", line 16: getting the value of `c'

VALUES AT THE END OF THE PATH:

p != 0 -- ERROR2 /*operating on NULL*/ >>>ERROR2_foo_af57809a2b615

"code2.cpp", line 17: invalid operation involving NULL pointer

ONE POSSIBLE PATH LEADING TO THE ERROR:

"code2.cpp", line 13: the if-condition is true (used as evidence that error is possible)

"code2.cpp", line 16: the if-condition is true

"code2.cpp", line 17: invalid operation `[]' involving NULL pointer `p'

VALUES AT THE END OF THE PATH:

c = 1 p = 0 a <= 0

2.3 內嵌程序

可以重載內存分配和釋放函數 new 和 delete,然後編寫程序定期統計內存的分配和釋放,從中找出可能的內存泄漏。或者調用系統函數定期監視程序堆的大小,關鍵要確定堆的增長是泄漏而不是合理的內存使用。這類方法比較複雜,在這就不給出詳細例子了。

3. 動態運行檢測

實時檢測工具主要有 valgrind, Rational purify 等。

3.1 Valgrind

Linux平台中調試C/C++內存泄漏方法 (騰訊和MTK面試的時候問到的) - 天天要聞

valgrind 是幫助程序員尋找程序里的 bug 和改進程序性能的工具。程序通過 valgrind 運行時,valgrind 收集各種有用的信息,通過這些信息可以找到程序中潛在的 bug 和性能瓶頸。

Valgrind 現在提供多個工具,其中最重要的是 Memcheck,Cachegrind,Massif 和 Callgrind。Valgrind 是在 Linux 系統下開發應用程序時用於調試內存問題的工具。它尤其擅長發現內存管理的問題,它可以檢查程序運行時的內存泄漏問題。其中的 memecheck 工具可以用來尋找 c、c++ 程序中內存管理的錯誤。可以檢查出下列幾種內存操作上的錯誤:

  • 讀寫已經釋放的內存
  • 讀寫內存塊越界(從前或者從後)
  • 使用還未初始化的變量
  • 將無意義的參數傳遞給系統調用
  • 內存泄漏

3.2 Rational purify

Rational Purify 主要針對軟件開發過程中難於發現的內存錯誤、運行時錯誤。在軟件開發過程中自動地發現錯誤,準確地定位錯誤,提供完備的錯誤信息,從而減少了調試時間。同 時也是市場上唯一支持多種平台的類似工具,並且可以和很多主流開發工具集成。Purify 可以檢查應用的每一個模塊,甚至可以查出複雜的多線程或進程應用中的錯誤。另外不僅可以檢查 C/C++,還可以對 Java 或 .NET 中的內存泄漏問題給出報告。

在 Linux 系統中,使用 Purify 需要重新編譯程序。通常的做法是修改 Makefile 中的編譯器變量。下面是用來編譯本文中程序的 Makefile:

CC=purify gcc

首先運行 Purify 安裝目錄下的 purifyplus_setup.sh 來設置環境變量,然後運行 make 重新編譯程序。

./purifyplus_setup.sh

下面給出編譯一個代碼文件的示例,源代碼文件命名為 test3.cpp. 用 purify 和 g++ 的編譯命令如下,‘-g’是編譯時加上調試信息。

purify g++ -g test3.cpp –o test

運行編譯生成的可執行文件 test,就可以得到圖1,可以定位出內存泄漏的具體位置。

./test

清單3. Purify 分析的代碼

#include <unistd.h> char * Logmsg;

int LeakTest(char * Para)

{

if(NULL==Para){

//local_log("LeakTest Func: empty parameter/n");

return -1;

}

Logmsg = new char[128];

for (int i = 0 ; i < 128; i++)

Logmsg[i] = i%64;

if(NULL == Logmsg){

//local_log("memeory allocation failed/n");

return -2;

}

sprintf(Logmsg,"LeakTest routine exit: '%s'./n", Para);

//local_log(Logmsg);

return 0;

}

int main(int argc,char **argv )

{

char szInit [] = "testcase1";

int i;

LeakTest(szInit);

for (i=0; i < 2; i++){

if(i%200 == 0)

LeakTest(szInit);

sleep(1);

} return 0;

}

需要指出的是,程序必須編譯成調試版本才可以定位到具體哪行代碼發生了內存泄漏。即在 gcc 或者 g++ 中,必須使用 "-g" 選項。

purify 的輸出結果

結論

本文介紹了多種內存泄漏,定位方法(包括靜態分析,動態實時檢測)。涉及到了多個工具,詳細描述的它們的用法、用途以及優缺點。對處理其它產品或項目內存泄漏相關的問題有很好的借鑒意義。

--------------------內存泄漏

在此,談論的是程序設計中內存泄漏和錯誤的問題,不過,並不是所有的程序都有這一問 題。首先,泄漏等一些內存方面的問題在有的程序語言中是不容易發生的。這些程序語言一般都認為內存管理太重要了,所以不能由程序員來處理,最好還是由程序 語言設計者來處理這些問題,這樣的語言有Perl、Java等等。

然而,在一些語言(最典型的就是C和C++)中,程序語言的設計者也認 為內存管理太重要,但必需由開發人員自己來處理。內存泄漏指的是程序員動態分配了內存,但是在使用完成後卻忘了將其釋放。除了內存泄漏以外,在開發人員自 己管理內存的開發中,緩衝溢出、懸擺指針等其它一些內存的問題也時有發生。

問題緣何產生

為了讓程序能夠處理在編譯時無法預知 的數據佔用內存的大小,所以程序必需要從操作系統實時地申請內存,這就是所謂的動態內存。這時候,就會出現程序申請到內存塊並且使用完成後,沒有將其歸還 給操作系統的錯誤。更糟的情況是所獲取的內存塊的地址丟失,從而系統無法繼續識別、定位該內存塊。還有其它的問題,比如試圖訪問已經釋放的指針(懸擺指 針),再如訪問已經被使用了的內存(內存溢出)的問題。

後果不容忽視

對於那些不常駐內存的程序來說,由於執行過程很短,所以 即使有漏洞可能也不會導致特別嚴重的後果。不過對於一些常駐內存的程序(比如Web服務器Apache)來說,如果出現這樣的問題,後果將非常嚴重。因為 有問題的程序會不斷地向系統申請內存,並且不釋放內存,最終可能導致系統內存耗盡而導致系統崩潰。此外,存在內存泄漏問題的程序除了會佔用更多的內存外, 還會使程序的性能急劇下降。對於服務器而言,如果出現這種情況,即使系統不崩潰,也會嚴重影響使用。

懸擺指針會導致一些潛在的隱患,並且 這些隱患不容易暴發。它非常不明顯,因此很難被發現。在這三種存在的問題形式中,緩衝溢出可能是最危險的。事實上,它可能會導致很多安全性方面的問題(一 個安全的程序包含很多要素,但是最重要的莫過於小心使用內存)。正如上面所述,有時也會發生同一內存塊被多次返還給系統的問題,這顯然也是程序設計上的錯 誤。一個程序員非常希望知道在程序運行的過程中,使用內存的情況,從而能夠發現並且修正問題。

如何處理

現在已經有了一些實時 監測內存問題的技術。內存泄漏問題可以通過定時地終止和重啟有問題的程序來發現和解決。在比較新的Linux內核版本中,有一種名為OOM(Out Of Memory )殺手的算法,它可以在必要時選擇執行Killed等程序。懸擺指針可以通過定期對所有已經返還給系統的內存置零來解決。解決內存溢出問題的方法則多種多 樣。

事實上,在程序運行時來解決這些問題,顯然要麻煩得多,所以我們希望能夠在開發程序時就發現並解決這些問題。下面介紹一些可用的自由軟件。

工具一:垃圾回收器(GC)

在GCC(下載)工具包中,有一個“垃圾回收器(GC)”,它可以輕鬆檢測並且修正很多的內存問題。目前該項目由HP的Hans-J.Boehm負責。

使用的技術

GC使用的是名為Boehm-Demers-Weiser的可以持續跟蹤內存定位的技術。它的算法通過使用標準的內存定位函數來實現。程序使用這些函數 進行編譯,然後執行,算法就會分析程序的操作。該算法非常著名並且比較容易理解,不會導致問題或者對程序有任何干擾。

性能

該工具有很好的性能,故可以有效提高程序效率。其代碼非常少並且可以直接在GCC中使用。

該工具沒有界面,使用起來比較困難,所以要想掌握它還是要花一些工夫的。一些現有的程序很有可能無法使用這個編輯器進行配置。此外,為了讓所有的調用能 被捕獲,所有的內存調用(比如malloc()和free())都必須要使用由GC提供的相應函數來代替。我們也可以使用宏來完成這一工作,但還是覺得不 夠靈活。

結論

如果你希望能夠有跨平台(體系結構、操作系統)的解決方案,那麼就是它了。

工具二:Memprof

Memprof(下載)是一個非常具有吸引力且非常易於使用的軟件,它由Red Hat的Owen Talyor創立。這個工具是用於GNOME前端的Boehm-Demers-Weiser垃圾回收器。

使用的技術

就其核心技術來說,Memprof和上面提到的GC沒有什麼本質的不同。不過在實現這一功能時,它是從程序中捕獲所有的內存請示並且實時將其重定位到垃圾回收器。

性能

該工具的性能非常不錯,其GUI設計得也不錯(如圖1所示)。這個工具直接就可以執行,並且其工作起來無需對源代碼進行任何修改。在程序執行時,這個工具會以圖形化的方式顯示內存的使用情況,以幫助你了解程序運行過程中內存的申請情況。

圖1 Memprof的GUI

該工具目前只能運行於x86和PPC體系結構之上的Linux系統之中。如果你需要用於其它的平台,應該想想使用其它的工具。該工具不是GTK應用程 序,所以需要一個完整的GNOME環境。這樣就使得其不能靈活用於所有的地方。此外,該工具的開發工作進展得也比較緩慢(現在是0.4.1版)。

結論

如果你喜歡GUI工具並且不介意只能用於Linux以及GNOME之下,該工具應該可以說是非常不錯。

=======================================================

註:需要C/C++ Linux服務器開發學習資料私信“資料”(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等),免費分享

科技分類資訊推薦

大模型到底是怎麼「思考」的?第一篇系統性綜述SAE的文章來了 - 天天要聞

大模型到底是怎麼「思考」的?第一篇系統性綜述SAE的文章來了

作者介紹:本篇文章的作者團隊來自美國四所知名高校:西北大學、喬治亞大學、新澤西理工學院和喬治梅森大學。第一作者束東與共同第一作者吳烜聖、趙海燕分別是上述高校的博士生,長期致力於大語言模型的可解釋性研究,致力於揭示其內部機制與 “思維” 過程
預售價23.58萬!全球首款L3級算力AI汽車小鵬G7首秀 - 天天要聞

預售價23.58萬!全球首款L3級算力AI汽車小鵬G7首秀

2025 年 6 月 11 日,小鵬汽車年度重磅新作 —— 小鵬 G7 在廣州舉行全球首秀,以 “全球首款 L3 級算力 AI 汽車” 的姿態正式亮相,同步開啟預售,Max 和 Ultra 兩個版本預售價均為 23.58 萬元。即日起至上市日,用戶支付 200
華為雲安全框架,助力企業笑對數字化浪潮下的安全挑戰 - 天天要聞

華為雲安全框架,助力企業笑對數字化浪潮下的安全挑戰

當企業逐漸發展壯大、業務系統日益龐雜的同時,面對着喜人的報表,那些隱藏在水下的安全隱患雖然不易被發覺,卻絕對無可忽視——Web應用暴露在公網,輕則遭遇惡意掃描和SQL注入,重則網站掛馬、數據泄露;網絡流量愈發複雜,傳統防護手段難以適配;主機和服務器成為入侵者的隱秘據點,挖礦、勒索、後門程序層出不窮。在這個...
京東&天貓&抖音同時登頂!萬和電氣618全渠道實現霸榜 - 天天要聞

京東&天貓&抖音同時登頂!萬和電氣618全渠道實現霸榜

2025年618電商大促已經圓滿收官,憑藉 “天生可靠”的品牌內核以及“產品驅動”的戰略主軸,萬和電氣在今年618交出了一份亮眼的成績單:公司在京東、天貓、抖音三大平台增速全面領跑,多款旗艦產品霸榜細分品類,其中,萬和安睡洗系列全渠道總銷量突破35000台,同比增長90%,展現出強勁的品牌競爭力和市場認可度。 基於“產...
逆勢增長62%!四特酒電商實力領航,以“特香”魅力開啟增長新一極 - 天天要聞

逆勢增長62%!四特酒電商實力領航,以“特香”魅力開啟增長新一極

當中國白酒行業步入深度調整期,傳統渠道增長放緩、消費場景結構性變化、年輕群體消費習慣遷移等多重因素交織,行業正經歷一場靜水深流的變革。據中國酒業協會數據顯示,2024年白酒行業全國規模以上企業白酒產量414.47萬千升,同比下降1.80%,銷售收入7963.84億元,同比增長5.30%,整體呈現“總量收縮、結構升級”特徵。 在...
快評丨程序員住車裡被質疑佔用公共資源,錯峰利用有何不可? - 天天要聞

快評丨程序員住車裡被質疑佔用公共資源,錯峰利用有何不可?

不僅不是對公共資源的擠占,反而在更大程度上是對公共設施的充分利用40多歲的程序員張運來可能沒想到,自己自詡愜意的生活方式,有一天會面臨“佔用公共資源”的指責。畢竟,他的短視頻賬號簽名都是“常駐深圳灣公園的車內露營引領者”。堅持“車內露營”近4年,面對媒體他表示,曾有網友質疑他長年佔用公園車位的做法,他...
一體抓實“三個管理”|以前頭疼的任務,現在輕鬆完成 - 天天要聞

一體抓實“三個管理”|以前頭疼的任務,現在輕鬆完成

《檢察日報》6月22日 頭版以前頭疼的任務,現在輕鬆完成福建永安:開發案卡智能核查軟件提升數據審核精準度每周五下午是福建省永安市檢察院業務數據核查專員小陳固定的案卡核查時間。以前,這是她“最怕”的任務——面對海量數據,人工核查耗時費力,一個
未來十年中國無人駕駛出租車規模將增長757倍,《公共出行自動駕駛規模化發展與治理》報告發布 - 天天要聞

未來十年中國無人駕駛出租車規模將增長757倍,《公共出行自動駕駛規模化發展與治理》報告發布

6月17日,在“自動駕駛汽車規模化發展與治理研討會”上,武漢大學數字經濟發展與治理論壇聯合武漢市社科院發布《從競速到落地:公共出行自動駕駛規模化發展與治理》。重點梳理自動駕駛產業發展的現狀、挑戰和趨勢,並結合公共出行自動駕駛汽車未來發展提出建議。 搶佔戰略機遇,自動駕駛汽車是新質生產力的典型代表 報告認...
強監管時代到來,對汽車行業意味着什麼? - 天天要聞

強監管時代到來,對汽車行業意味着什麼?

6月10日晚,中國第一汽車集團有限公司、東風汽車集團有限公司、廣州汽車集團股份有限公司、賽力斯集團股份有限公司等四家汽車生產企業分別發表聲明,就“支付賬期不超過60天”作出承諾。