系統程式設計中一個重要的方面就是有效地處理與記憶體相關的問題。你的工作越接近系統,你就需要面對越多的記憶體問題。有時這些問題非常瑣碎,而更多時候它會演變成一個調試記憶體問題的惡夢。是以,在實踐中會用到很多工具來調試記憶體問題。
Valgrind是運作在Linux上一套基于仿真技術的程式調試和分析工具,它包含一個核心——一個軟體合成的CPU,和一系列的小工具,每個工具都可以完成一項任務──調試,分析,或測試等。Valgrind可以檢測記憶體洩漏和記憶體違例,還可以分析cache的使用等,靈活輕巧而又強大,能直穿程式錯誤的心髒,真可謂是程式員的瑞士軍刀。
Valgrind工具包包含多個工具:
- Memcheck是一個記憶體錯誤檢測器。它有助于使你的程式,尤其是那些用C和C++寫的程式,更加準确。
- Cachegrind是一個緩存和分支預測分析器。它有助于使你的程式運作更快。
- Callgrind是一個調用圖緩存生成分析器。它與Cachegrind的功能有重疊,但也收集Cachegrind不收集的一些資訊
- Helgrind是一個線程錯誤檢測器。它有助于使你的多線程程式更加準确。
- DRD也是一個線程錯誤檢測器。它和Helgrind相似,但使用不同的分析技術,是以可能找到不同的問題。
- Massif是一個堆分析器。它有助于使你的程式使用更少的記憶體。
- DHAT是另一種不同的堆分析器。它有助于了解塊的生命期、塊的使用和布局的低效等問題。
- SGcheck是一個實驗工具,用來檢測堆和全局數組的溢出。它的功能和Memcheck互補:SGcheck找到Memcheck無法找到的問題,反之亦然。
- BBV是個實驗性質的SimPoint基本塊矢量生成器。它對于進行計算機架構的研究和開發很有用處。
這裡給大家介紹如何使用Valgrind memcheck工具進行C/C++的記憶體洩漏檢測。memcheck工具主要檢查下面的程式錯誤:
- 使用未初始化的記憶體 (Use of uninitialised memory)
- 使用已經釋放了的記憶體 (Reading/writingmemory after it has been free’d)
- 使用超過 malloc配置設定的記憶體空間(Reading/writing off the end of malloc’d blocks)
- 對堆棧的非法通路 (Reading/writinginappropriate areas on the stack)
- 申請的空間是否有釋放 (Memory leaks –where pointers to malloc’d blocks are lost forever)
- malloc/free/new/delete申請和釋放記憶體的比對(Mismatched use of malloc/new/new [] vs free/delete/delete [])
- src和dst的重疊(Overlapping src and dst pointers in memcpy() and related functions)
這幾個工具的使用是通過指令:valgrind --tool=name 程式名來分别調用的,當不指定tool參數時預設是 --tool=memcheck 。
使用前,需要保證valgrind已經安裝:
如果是ubuntu系統,線上安裝即可:
使用 valgrind memcheck 方式:
- 編譯代碼時,加上調試參數 -g (用來在memcheck的輸出中生成行号,如果出現錯誤,可以定位到錯誤出現的位置)
- --leak-check=full 指的是完全檢查記憶體洩漏
- a.out 為需要檢查的可執行程式,有些系統(如ubuntu),如果加上“./”:
1. 使用未初始化的記憶體
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p;
char c = *p; //使用未初始化的記憶體
printf("\n [%c]\n",c);
return 0;
}
在上面的代碼中,我們嘗試使用未初始化的指針“p”,讓我們運作 Memcheck 來檢查結果:
2. 在記憶體被釋放後進行讀/寫
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
free(p);
c = *p;//在記憶體被釋放後進行讀/寫
return 0;
}
調試結果如下:
3. 從已配置設定記憶體塊的尾部進行讀/寫
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *(p+1); //從已配置設定記憶體塊的尾部進行讀/寫
printf("\n [%c]\n",c);
free(p);
return 0;
}
調試結果如下:
4. 記憶體洩露
#include <stdio.h>
#include <stdlib.h>
//在這次的代碼中, 我們申請了一個位元組但是沒有将它釋放
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
return 0;
}
調試結果如下:
5. 不比對地使用malloc/new/new[] 和 free/delete/delete[]
#include <stdio.h>
#include <stdlib.h>
#include<iostream>
int main(void)
{
char *p = (char*)malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
delete p;
return 0;
}
上面的代碼中,我們使用了malloc()來配置設定記憶體,但是使用了delete操作符來删除記憶體。
注意: 使用g++來編譯上面的代碼,因為delete操作符是在C++中引進的,而要編譯C++需要使用g++。
調試結果如下:
6. 兩次釋放記憶體
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p = (char*)malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
free(p);
free(p);
return 0;
}
調試結果如下: