1. 內(nèi)存溢出
內(nèi)存溢出 OOM (out of memory),是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory;比如申請了一個int,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出。
2. 內(nèi)存泄漏
內(nèi)存泄露 memory leak,是指程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴重,無論多少內(nèi)存,遲早會被占光。最終的結果就是導致OOM。
內(nèi)存泄漏是指你向系統(tǒng)申請分配內(nèi)存進行使用(new),可是使用完了以后卻不歸還(delete),結果你申請到的那塊內(nèi)存你自己也不能再訪問(也許你把它的地址給弄丟了),而系統(tǒng)也不能再次將它分配給需要的程序。
3. 造成內(nèi)存泄露常見的三種情況
1,指針重新賦值
2,錯誤的內(nèi)存釋放
3,返回值的不正確處理
3.1 指針重新賦值
如下代碼:
char * np = (char *)malloc(10);
其中,指針變量 p 和 np 分別被分配了 10 個字節(jié)的內(nèi)存。
如果程序需要執(zhí)行如下賦值語句:
這時候,指針變量 p 被 np 指針重新賦值,其結果是 p 以前所指向的內(nèi)存位置變成了孤立的內(nèi)存。它無法釋放,因為沒有指向該位置的引用,從而導致 10 字節(jié)的內(nèi)存泄漏。
因此,在對指針賦值前,一定確保內(nèi)存位置不會變?yōu)楣铝⒌摹?/p>
類似的情況,連續(xù)重復new的情況也是類似:
p = new int...;//錯誤
3.2 錯誤的內(nèi)存釋放
假設有一個指針變量 p,它指向一個 10 字節(jié)的內(nèi)存位置。該內(nèi)存位置的第三個字節(jié)又指向某個動態(tài)分配的 10 字節(jié)的內(nèi)存位置。
如果程序需要執(zhí)行如下賦值語句時:
很顯然,如果通過調(diào)用 free 來釋放指針 p,則 np 指針也會因此而變得無效。np 以前所指向的內(nèi)存位置也無法釋放,因為已經(jīng)沒有指向該位置的指針。換句話說,np 所指向的內(nèi)存位置變?yōu)楣铝⒌?,從而導致?nèi)存泄漏。
因此,每當釋放結構化的元素,而該元素又包含指向動態(tài)分配的內(nèi)存位置的指針時,應首先遍歷子內(nèi)存位置(如本示例中的 np),并從那里開始釋放,然后再遍歷回父節(jié)點,如下面的代碼所示:
free(p);
3.3 返回值的不正確處理
有時候,某些函數(shù)會返回對動態(tài)分配的內(nèi)存的引用,如下面的示例代碼所示:
return (char *)malloc(10);
}
void f1(){
f();
}
函數(shù) f1 中對 f 函數(shù)的調(diào)用并未處理該內(nèi)存位置的返回地址,其結果將導致 f 函數(shù)所分配的 10 個字節(jié)的塊丟失,并導致內(nèi)存泄漏。
4 在內(nèi)存分配后忘記使用 free 進行釋放
4. 如何避免內(nèi)存泄露?
- 確保沒有在訪問空指針。
- 每個內(nèi)存分配函數(shù)都應該有一個 free 函數(shù)與之對應,alloca 函數(shù)除外。
- 每次分配內(nèi)存之后都應該及時進行初始化,可以結合 memset 函數(shù)進行初始化,calloc 函數(shù)除外。
- 每當向指針寫入值時,都要確保對可用字節(jié)數(shù)和所寫入的字節(jié)數(shù)進行交叉核對。
- 在對指針賦值前,一定要確保沒有內(nèi)存位置會變?yōu)楣铝⒌摹?/li>
- 每當釋放結構化的元素(而該元素又包含指向動態(tài)分配的內(nèi)存位置的指針)時,都應先遍歷子內(nèi)存位置并從那里開始釋放,然后再遍歷回父節(jié)點。
- 始終正確處理返回動態(tài)分配的內(nèi)存引用的函數(shù)返回值。
5.定位內(nèi)存泄漏(valgrind)(重點)
5.1、基本概念
Valgrind是一個GPL的軟件,用于Linux(For x86, amd64 and ppc32)程序的內(nèi)存調(diào)試和代碼剖析。你可以在它的環(huán)境中運行你的程序來監(jiān)視內(nèi)存的使用情況,比如C 語言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包,你可以自動的檢測許多內(nèi)存管理和線程的bug,避免花費太多的時間在bug尋找上,使得你的程序更加穩(wěn)固。
安裝Valgrind
http://valgrind.org/downloads/valgrind-3.12.0.tar.bz2
valgrind安裝:
1. tar -jxvf valgrind-3.12.0.tar.bz2
2. cd valgrind-3.12.0
3. ./configure
4. make
5. sudo make install
應用環(huán)境:Linux
編程語言:C/C++
使用方法:編譯時加上-g選項,如 gcc -g filename.c -o filename,使用如下命令檢測內(nèi)存使用情況:
valgrind --tool=memcheck --leak-check=full ./test
valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./filename
其中--leak-check=full指的是完全檢查內(nèi)存泄漏,--show-reachable=yes是顯示內(nèi)存泄漏的地點,--trace-children=yes是跟入子進程。
如果您的程序是會正常退出的程序,那么當程序退出的時候valgrind自然會輸出內(nèi)存泄漏的信息。如果您的程序是個守護進程,那么也不要緊,我們 只要在別的終端下殺死m(xù)emcheck進程(因為valgrind默認使用memcheck工具,就是默認參數(shù)--tools=memcheck)
參數(shù)選擇
memcheck ------> 這是valgrind應用最廣泛的工具,一個重量級的內(nèi)存檢查器,能夠發(fā)現(xiàn)開發(fā)中絕大多數(shù)內(nèi)存錯誤使用情況,比如:使用未初始化的內(nèi)存,使用已經(jīng)釋放了的內(nèi)存,內(nèi)存訪問越界等。
callgrind ------> 它主要用來檢查程序中函數(shù)調(diào)用過程中出現(xiàn)的問題。
cachegrind ------> 它主要用來檢查程序中緩存使用出現(xiàn)的問題。
helgrind ------> 它主要用來檢查多線程程序中出現(xiàn)的競爭問題。
massif ------> 它主要用來檢查程序中堆棧使用中出現(xiàn)的問題。
extension ------> 可以利用core提供的功能,自己編寫特定的內(nèi)存調(diào)試工具
-h –help 顯示幫助信息。
-version 顯示valgrind內(nèi)核的版本,每個工具都有各自的版本。
-q –quiet 安靜地運行,只打印錯誤信息。
-v –verbose 更詳細的信息, 增加錯誤數(shù)統(tǒng)計。
-trace-children=no|yes 跟蹤子線程? [default: no]
-track-fds=no|yes 跟蹤打開的文件描述?[default: no]
-time-stamp=no|yes 增加時間戳到LOG信息? [default: no]
-log-fd= 輸出LOG到描述符文件 [2=stderr]
-log-file= 將輸出的信息寫入到filename.PID的文件里,PID是運行程序的進行ID
-log-file-exactly= 輸出LOG信息到 file
-log-file-qualifier= 取得環(huán)境變量的值來做為輸出信息的文件名。[none]
-log-socket=ipaddr:port 輸出LOG到socket ,ipaddr:port
LOG信息輸出
-xml=yes 將信息以xml格式輸出,只有memcheck可用
-num-callers= show callers in stack traces [12]
-error-limit=no|yes 如果太多錯誤,則停止顯示新錯誤? [yes]
-error-exitcode= 如果發(fā)現(xiàn)錯誤則返回錯誤代碼 [0=disable]
-db-attach=no|yes 當出現(xiàn)錯誤,valgrind會自動啟動調(diào)試器gdb。[no]
-db-command= 啟動調(diào)試器的命令行選項[gdb -nw %f %p]
設計思路:根據(jù)軟件的內(nèi)存操作維護一個有效地址空間表和無效地址空間表(進程的地址空間)
5.2、多個工具
1、Memcheck
最常用的工具,用來檢測程序中出現(xiàn)的內(nèi)存問題,所有對內(nèi)存的讀寫都會被檢測到,一切對malloc()/free()/new/delete的調(diào)用都會被捕獲。所以,Memcheck 工具主要檢查下面的程序錯誤
能夠檢測:
- 使用未初始化的內(nèi)存 (Use of uninitialised memory)
- 使用已經(jīng)釋放了的內(nèi)存 (Reading/writing memory after it has been free’d)
- 使用超過 malloc分配的內(nèi)存空間(Reading/writing off the end of malloc’d blocks)
- 對堆棧的非法訪問 (Reading/writing inappropriate areas on the stack)
- 申請的空間是否有釋放 (Memory leaks – where pointers to malloc’d blocks are lost forever)
- malloc/free/new/delete申請和釋放內(nèi)存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])
- src和dst的重疊(Overlapping src and dst pointers in memcpy() and related functions)
- 重復free
Callgrind
和gprof類似的分析工具,但它對程序的運行觀察更是入微,能給我們提供更多的信息。和gprof不同,它不需要在編譯源代碼時附加特殊選項,但加上調(diào)試選項是推薦的。Callgrind收集程序運行時的一些數(shù)據(jù),建立函數(shù)調(diào)用關系圖,還可以有選擇地進行cache模擬。在運行結束時,它會把分析數(shù)據(jù)寫入一個文件。callgrind_annotate可以把這個文件的內(nèi)容轉化成可讀的形式。
Cachegrind
Cache分析器,它模擬CPU中的一級緩存I1,Dl和二級緩存,能夠精確地指出程序中cache的丟失和命中。如果需要,它還能夠為我們提供cache丟失次數(shù),內(nèi)存引用次數(shù),以及每行代碼,每個函數(shù),每個模塊,整個程序產(chǎn)生的指令數(shù)。這對優(yōu)化程序有很大的幫助。
Helgrind
它主要用來檢查多線程程序中出現(xiàn)的競爭問題。Helgrind尋找內(nèi)存中被多個線程訪問,而又沒有一貫加鎖的區(qū)域,這些區(qū)域往往是線程之間失去同步的地方,而且會導致難以發(fā)掘的錯誤。Helgrind實現(xiàn)了名為“Eraser”的競爭檢測算法,并做了進一步改進,減少了報告錯誤的次數(shù)。不過,Helgrind仍然處于實驗階段。
Massif
堆棧分析器,它能測量程序在堆棧中使用了多少內(nèi)存,告訴我們堆塊,堆管理塊和棧的大小。Massif能幫助我們減少內(nèi)存的使用,在帶有虛擬內(nèi)存的現(xiàn)代系統(tǒng)中,它還能夠加速我們程序的運行,減少程序停留在交換區(qū)中的幾率。
5.3、使用原理
Memcheck 能夠檢測出內(nèi)存問題,關鍵在于其建立了兩個全局表。
1、Valid-Value 表:
對于進程的整個地址空間中的每一個字節(jié)(byte),都有與之對應的 8 個 bits;對于 CPU 的每個寄存器,也有一個與之對應的 bit 向量。這些 bits 負責記錄該字節(jié)或者寄存器值是否具有有效的、已初始化的值。
2、Valid-Address 表
對于進程整個地址空間中的每一個字節(jié)(byte),還有與之對應的 1 個 bit,負責記錄該地址是否能夠被讀寫。
檢測原理:
- 當要讀寫內(nèi)存中某個字節(jié)時,首先檢查這個字節(jié)對應的 A bit。如果該A bit顯示該位置是無效位置,memcheck 則報告讀寫錯誤。
- 內(nèi)核(core)類似于一個虛擬的 CPU 環(huán)境,這樣當內(nèi)存中的某個字節(jié)被加載到真實的 CPU 中時,該字節(jié)對應的 V bit也被加載到虛擬的 CPU 環(huán)境中。一旦寄存器中的值,被用來產(chǎn)生內(nèi)存地址,或者該值能夠影響程序輸出,則 memcheck 會檢查對應的V bits,如果該值尚未初始化,則會報告使用未初始化內(nèi)存錯誤。
5.4、具體使用
- 使用未初始化的內(nèi)存(使用野指針)
這里我們定義了一個指針p,但并未給他開辟空間,即他是一個野指針,但我們卻使用它了
Valgrind檢測出我們程序使用了未初始化的變量,但并未檢測出內(nèi)存泄漏。
2.在內(nèi)存被釋放后進行讀/寫(使用野指針)
p所指向的內(nèi)存被釋放了,p變成了野指針,但是我們卻繼續(xù)使用這片內(nèi)存。
Valgrind檢測出我們使用了已經(jīng)free掉的內(nèi)存,并給出這片內(nèi)存是哪里分配哪里釋放的。
3.從已分配內(nèi)存塊的尾部進行讀/寫(動態(tài)內(nèi)存越界)
我們動態(tài)地分配了一段數(shù)組,但我們在訪問個數(shù)組時發(fā)生了越界讀寫,程序crash掉。
Valgrind檢測出越界的位置。
注意:Valgrind不檢查靜態(tài)分配數(shù)組的使用情況!所以對靜態(tài)分配的數(shù)組,Valgrind表示無能為力!比如下面的例子,程序crash掉,我們卻不知道為什么。
4.內(nèi)存泄漏
內(nèi)存泄漏的原因在于沒有成對地使用malloc/free和new/delete,比如下面的例子。
Valgrind會給出程序中malloc和free的出現(xiàn)次數(shù)以判斷是否發(fā)生內(nèi)存泄漏,比如對上面的程序運行memcheck,Valgrind的記錄顯示上面的程序用了1次malloc,卻調(diào)用了0次free,明顯發(fā)生了內(nèi)存泄漏!
上面提示了我們可以使用–leak-check=full進一步獲取內(nèi)存泄漏的信息,比如malloc和free的具體行號。
- 不匹配地使用malloc/new/new[] 和 free/delete/delete[]
正常使用new/delete和malloc/free是這樣子的:
而不匹配地使用malloc/new/new[] 和 free/delete/delete[]則會被提示mismacth:
6.兩次釋放內(nèi)存
double free的情況同樣是根據(jù)malloc/free的匹配對數(shù)來體現(xiàn)的,比如free多了一次,Valgrind也會提示。
-
內(nèi)存
+關注
關注
8文章
3025瀏覽量
74055 -
程序
+關注
關注
117文章
3787瀏覽量
81049 -
函數(shù)
+關注
關注
3文章
4331瀏覽量
62622 -
C 語言
+關注
關注
0文章
18瀏覽量
14227
發(fā)布評論請先 登錄
相關推薦
評論