文章目錄
1 前言
C/C++相比其他高級編程語言,具有指針的概念,指針即是內存地址。C/C++可以通過指針來直接訪問內存空間,效率上的提升是不言而喻的,是其他高級編程語言不可比擬的;比如訪問內存一段數據,通過指針可以直接從內存空間讀取數據,避免了中間過程函數壓棧、數據拷貝甚至消息傳輸等待。指針是C/C++的優勢,但也是一個隱患,指針是一把雙刃劍,由於內存交給了程序員管理,這是存在隱患的;人嘛總會有疏忽的時候,如果申請了內存,一個疏忽忘記釋放了,未釋放的內存將不能被系統再申請使用,即是一直佔用又不能使用,俗稱內存泄露;久而久之,系統長時間運行後將申請不到內存,系統上所有任務執行失敗,只能重啓系統。內存交給了程序員管理,除了內存泄露的情況外,還可能引起其他的問題,總結起來包括如下幾點。
- 訪問沒有申請內存的空指針(空指針)
- 訪問已釋放內存的指針(野指針)
- 內存越界訪問
- 內存泄露,申請了內存沒有釋放
- 重複釋放內存
雖然動態內存(這裏默認指的是堆上的動態內存,由程序員管理;棧上的動態內存由系統管理)存在一定的隱患,可靠性取決於程序員的認真和細心,但因爲其具有優良的靈活性和高效率以及內存複用,在實際應用中,對於一些動態數據交互場合,動態內存往往是首選。因此,一方面除了程序員本身的謹慎使用,另一方面也湧現出各類“內存異常“的檢測工具,畢竟當代碼量數以萬計時,依靠人力去檢查是不切實際的。
2 常用內存檢測工具
雖然動態內存存在隱患,但有時候又不得不使用,內存泄露也就成爲一個必須杜絕的敏感問題;有需求就會有相應的解決方法,目前已存在許多優秀的內存泄露檢測工具,除了我們常用的Valgrind外,還有mtrace、dmalloc、memwatch等優秀工具。
工具 | 描述 |
---|---|
valgrind | 一個強大開源的程序檢測工具集合 |
mtrace | GNU擴展, 用來跟蹤malloc, mtrace爲內存分配函數(malloc, realloc, memalign, free)安裝hook函數 |
dmalloc | 用於檢查C/C++內存泄露(leak)的工具,即檢查是否存在直到程序運行結束還沒有釋放的內存,以一個運行庫的方式發佈 |
memwatch | 和dmalloc一樣,它能檢測未釋放的內存、同一段內存被釋放多次、位址存取錯誤及不當使用未分配之內存區域 |
mpatrol | 一個跨平臺的 C++ 內存泄漏檢測器 |
dbgmem | |
Electric Fence |
3 Valgrind工具
Valgrind是一款基於linux平臺開源的內存檢測工具集合,功能強大,使用廣泛。利用Valgrind工具,在檢測出文章開頭提及的動態內存隱患,即是Valgrind工具中的內存檢測組件Memcheck, Memcheck支持的功能包括:
- 使用空指針
- 使用野指針
- 內存空間訪問越界
- 內存空間未釋放
- 內存空間重複釋放
- 內存空間申請和釋放不匹配
Valgrind功能強大,是一個工具集合,除了Memcheck工具外,還提供Cachegrind、Helgrind、Callgrind、Massif工具。
- Cachegrind
與gprof類似的分析工具,但它對程序的運行觀察更是入微,能給我們提供更多的信息。和gprof不同,它不需要在編譯源代碼時附加特殊選項,但加上調試選項是推薦的。Callgrind收集程序運行時的一些數據,建立函數調用關係圖,還可以有選擇地進行cache模擬。在運行結束時,它會把分析數據寫入一個文件。callgrind_annotate可以把這個文件的內容轉化成可讀的形式。
- Helgrind
Cache分析器,它模擬CPU中的一級緩存I1,Dl和二級緩存,能夠精確地指出程序中cache的丟失和命中。如果需要,它還能夠爲我們提供cache丟失次數,內存引用次數,以及每行代碼,每個函數,每個模塊,整個程序產生的指令數。這對優化程序有很大的幫助。
- Callgrind
它主要用來檢查多線程程序中出現的競爭問題。Helgrind尋找內存中被多個線程訪問,而又沒有一貫加鎖的區域,這些區域往往是線程之間失去同步的地方,而且會導致難以發掘的錯誤。Helgrind實現了名爲“Eraser”的競爭檢測算法,並做了進一步改進,減少了報告錯誤的次數。不過,Helgrind仍然處於實驗階段。
- Massif
堆棧分析器,它能測量程序在堆棧中使用了多少內存,告訴我們堆塊,堆管理塊和棧的大小。Massif能幫助我們減少內存的使用,在帶有虛擬內存的現代系統中,它還能夠加速我們程序的運行,減少程序停留在交換區中的機率。
- extension
可以根據core提供的功能,編寫自定義的內存調試工具 。
以上工具集介紹引自Linux下幾款C++程序中的內存泄露檢查工具文章,更新詳細的介紹可以閱讀原文,總結得非常好。
4 使用Valgrind工具檢測內存
Valgrind內存檢測的原理是,Valgrind實現一個程序運行的虛擬環境,待檢測的應用程序在Valgrind的虛擬環境中運行,Valgrind能夠實時監測程序中內存申請、使用、釋放的情況,並記錄一些正常運行的有效信息和可能存在內存異常的信息,輸出到終端或者用戶指定的文件中。
4.1 Valgrind安裝
Valgrind工具實用且使用廣泛,大多數主流linux系統發行版都已集成Valgrind工具,在終端輸入“valgrind”
,如系統沒有安裝Valgrind工具會提示以下安裝信息。
acuity@ubuntu:~$ valgrind
The program 'valgrind' is currently not installed. You can install it by typing:
sudo apt install valgrind
Valgrind安裝可以下載源碼後安裝,也通過系統源在線安裝,Ubuntu推薦在線安裝。
sudo apt install valgrind
安裝完成,輸入“valgrind”
命令,提示以下信息,表示安裝成功。
acuity@ubuntu:~$ valgrind
valgrind: no program specified
valgrind: Use --help for more information.
4.2 使用說明
- 待檢測的程序編譯時需加入
"-g"
,保留調試信息 - 檢測命令格式:
valgrind --tool=memcheck --leak-check=full ./file
,–leak-check=full
表示檢測所有內存泄露 - 檢測記錄除了輸出到終端,還可以輸出到文件,這樣可以監測程序長時間運行後出現異常,通過查閱文件記錄分析問題
- 更多的使用說明或者忘記命令時,可以執行
valgrind -h
查看幫助信息
4.3 檢測程序使用空指針
例子uninit.c
:
#include <stdio.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
printf("address [0x%p]\r\n", p);
*p = 0;
return 0;
}
訪問空指針,特別是往空指針寫數據時,基本會引起段錯誤(Segmentation fault ),此時系統會產生core dump 文件,通過 core dump文件結合GDB工具也很容易找出訪問空指針的地方。
4.4 檢測程序使用野指針
例子free.c
:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = malloc(4);
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
free(p);
*p = 0;
return 0;
}
4.5 檢測程序內存空間訪問越界
例子over.c
:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = malloc(4);
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
p[4] = 0;
free(p);
return 0;
}
4.6 檢測程序內存空間未釋放
例子leak.c
:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = malloc(4);
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
return 0;
}
4.7 檢測程序內存空間重複釋放
例子refree.c
:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = malloc(4);
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
free(p);
free(p);
return 0;
}
4.8 檢測程序內存空間申請與釋放不匹配
例子nolike.c
:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = (int*)malloc(sizeof(int));
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
delete p;
return 0;
}
4.9 小結
內存泄露檢測包括動態內存使用的規範性,根本的解決辦法是程序員保持良好的編碼習慣,使用動態內存時謹慎考慮,保證申請與釋放的必然性。因爲,一些隱晦的問題可能需要在特定條件下才會引起內存泄露,依賴於檢測工具也是需要長時間運行軟件才能發現。
Valgrind—memcheck工具更多是用於檢測內存泄露,空指針、野指針、內存越界、重複釋放等問題,會引系統段錯誤(Valgrind),使用GDB結合系統產生的core dump文件,也能快速定位到調用位置。而內存泄露不會立即導致系統異常,只有運行一定時間後系統申請不到內存時纔會引起異常。因此,藉助Valgrind—memcheck工具來檢測內存泄露是一個高效的方法之一。
5 參考文章
【2】Valgrind學習總結