使用 Visual Studio 检测内存泄漏

在 Visual Studio 中使用 C Run-time Library (CRT) 检测内存泄漏。

内存泄漏是 C/C++ 中最难发现的 BUG 之一,通常难以被注意到,使用 Visual Studio 和 C Run-time Library (CRT) 可以帮助我们发现内存泄漏问题。

1 启用内存泄漏检测

要启用内存泄漏检测,只需在你的程序前加上

1
2
3
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

其中,#define _CRTDBG_MAP_ALLOC 的作用是显示详细信息(如内存泄漏发生的文件、发生的具体行号),如果省略该语句,将不会输出详细信息。

#include crtdbg.h 将内存分配时使用的 mallocfree 函数分别改变为 _malloc_dbg_free_dbg。在使用上,二者是相同的,后者只是比前者多了跟踪内存分配的功能,因此可以检测内存泄漏。

添加上述语句后,还需要在应用出口点前加上 _CrtDumpMemoryLeaks,以输出内存泄漏报告。

1
_CrtDumpMemoryLeaks();

2 例子

现在来看一个简单的例子,编写如下程序:

1
2
3
4
5
6
7
8
9
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

int main() {
int* var = new int[10];
_CrtDumpMemoryLeaks();
return 0;
}

注意要以调试模式运行该程序,如果不以调试模式运行则不会显示内存泄漏报告。

可以看到以下输出

1
2
3
4
5
Detected memory leaks!
Dumping objects ->
{105} normal block at 0x00D68830, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

可以看到,第一行 Detected memory leaks! 提示我们发现了内存泄漏。

  • {105} 是内存分配编号。
  • normal block 是类型,其他的类型还有 free, ignore 等,详细见参考资料。
  • 0x00D68830 是内存泄漏的地址。
  • 40 bytes 是内存泄漏的大小,因为我们分配了 10 个 int,正好是 40 bytes
  • CD CD CD ... 是这块内存中的数据(十六进制表示)。

3 常见问题

3.1 #define _CRTDBG_MAP_ALLOC 不输出详细信息

可以观察到,虽然我们在代码中添加了 #define _CRTDBG_MAP_ALLOC,输出中仍然没有出现详细信息。如果没有显示代码的位置和行号,那么非常不利于我们调试代码。

解决方法是添加如下语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRTDBG_MAP_ALLOC
#ifdef _DEBUG
#define MYDEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__)
#define new MYDEBUG_NEW
#else
#define MYDEBUG_NEW
#endif

#include <stdlib.h>
#include <crtdbg.h>

int main() {
int* var = new int[10];
_CrtDumpMemoryLeaks();
return 0;
}

再次以调试模式运行该程序,发现正确输出了文件名行号信息

1
2
3
4
5
Detected memory leaks!
Dumping objects ->
C:\path\main.cpp(13) : {105} normal block at 0x009F8830, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

可以看到内存泄漏发生在 main.cpp 的 13 行处。

3.2 使用 libeigen 时检测到内存泄漏

在使用 Eigen 库时,会检测到内存泄漏,如下代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 前面的重复代码省略
#include <iostream>
#include <eigen3/Eigen/Dense>

int main()
{
using RowMajorMat = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
RowMajorMat a(1024, 4096);
RowMajorMat b(4096, 784);
const auto c = a * b;
std::cout << c(0, 0) << std::endl;
_CrtDumpMemoryLeaks();
}

发现内存泄漏

1
2
3
4
5
6
7
Detected memory leaks!
Dumping objects ->
C:\path\eigen3\Eigen\src\Core\util\Memory.h(88) : {176} normal block at 0x02446040, 12845072 bytes long.
Data: < @`D > CD CD CD CD CD CD CD CD CD CD CD CD 40 60 44 02
C:\path\eigen3\Eigen\src\Core\util\Memory.h(88) : {175} normal block at 0x01338040, 16777232 bytes long.
Data: < @ 3 > CD CD CD CD CD CD CD CD CD CD CD CD 40 80 33 01
Object dump complete.

但是实际上并没有发生内存泄漏,这是由于变量 a, b, c 在执行 _CrtDumpMemoryLeaks 还没有被销毁。解决方法是加上一个大括号。

1
2
3
4
5
6
7
8
9
10
11
int main()
{
{
using RowMajorMat = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
RowMajorMat a(1024, 4096);
RowMajorMat b(4096, 784);
const auto c = a * b;
std::cout << c(0, 0) << std::endl;
}
_CrtDumpMemoryLeaks();
}

修改后未检测到内存泄漏。

4 参考资料

原创技术分享,您的支持将鼓励我继续创作