【代碼調試】如何使用Valgrind檢測內存泄漏



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 參考文章

【1】Linux下幾款C++程序中的內存泄露檢查工具

【2】Valgrind學習總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章