内存泄漏相关

pre

  • 一个古老的windows项目查内存泄漏相关问题

vld windows 安装

  • 下载:vld-2.5.1-setup.exe
  • 实测 vs2019、vs2022 均可用,注意载入的 lib 是 win32/win64 就好
  • 默认目录:C:\Program Files (x86)\Visual Leak Detector

project 载入:

  • c/c++ -> general -> additional include directories: C:\Program Files (x86)\Visual Leak Detector\include
  • c/c++ -> general -> debug information format: Program Database(/Zi)
  • linker -> input -> additional dependencies: C:\Program Files (x86)\Visual Leak Detector\lib\Win64\vld.lib
  • linker -> Debugging -> generate debug info: (/Debug: Full)
  • 注意:如果是在 vs 运行状态下安装 vld,需要重启 vs;否则工程运行会报错找不到 vld.dll 类似的问题

代码修改:

  • main 函数所在 cpp 文件最上面添加 #include <vld.h>
  • 如果是使用 stdafx.h 的项目,在 stdafx.h 里最前面添加
  • 重定向 stdout 到文件,以便查看最后的输出
  • 手动开启 vld,这样方便非debug模式显式的关闭 vld
    1
    2
    3
    4
    5
    6
    7
    8
    // main 函数开始的地方:
    freopen("vld-output.txt", "w", stdout);

    // 显式开启vld
    VLDEnable();

    // main 函数结束的地方
    fflush(stdout);

linux 环境还是 libasan/tcmalloc 更好用

libasan

  • 物料:gcc 版本自带(高版本)
  • makefile 示例: (注意CXXFLAGS, LINKFLAGS都需要带这个参数)
    1
    2
    3
    ASANFLAGS := -fsanitize=leak -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls
    CXXFLAGS += $(ASANFLAGS)
    LINKFLAGS += $(ASANFLAGS)

tcmalloc

  • 物料:google gpreftools 工具包

  • makefile 示例: (注意: 加到最后)

    1
    LINKFLAGS += -ltcmalloc
  • tips: 线程数量超过12左右启动会慢

  • 方式1: 直接开, 结束进程后会给出解析heap文件的指令

    1
    2
    3
    4
    5
    # 开启
    env HEAPCHECK=normal ./memory-leak-test

    # 结果
    pprof ./memory-leak-test "/tmp/memory-leak-test.23613._main_-end.heap" --stack --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --text > tcmalloc-mlt.output.txt
  • 方式2: 查运行时泄漏

    • 分配内存,占用内存参数都设置的很大,避免自己打heap
    • 设置了信号量12打印一次
    • 有两个文件了可以对比分析
      1
      2
      3
      4
      5
      # 启动
      env HEAPCHECK=normal HEAPPROFILE=./mlt.prof HEAP_PROFILE_ALLOCATION_INTERVAL=107374182400 HEAP_PROFILE_INUSE_INTERVAL=1073741824000 HEAPPROFILESIGNAL=12 ./memory-leak-test

      # 结果
      pprof --text --stack memory-leak-test mlt.prof.0002.heap --base=mlt.prof.0001.heap > tcmalloc-mlt.output.txt

一些常见的内存泄漏问题:(AI 辅助生成)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// ---------------- tips ----------------
void memoryLeakExample() {
int* p = new int(10); // 动态分配内存
// 忘记释放内存,导致内存泄漏
}

// ---------------- tips ----------------
void memoryLeakWithException() {
int* p = new int(10); // 动态分配内存
throw std::runtime_error("Something went wrong!"); // 抛出异常
delete p; // 由于异常,delete 不会被执行,造成内存泄漏
}

// ---------------- tips ----------------
void memoryLeakWithMultipleAllocations() {
int* p = new int(10); // 分配内存
p = new int(20); // 再次分配内存,但没有释放之前的内存,造成泄漏
delete p; // 仅释放了最后分配的内存
}

// ---------------- tips ----------------
void memoryLeakInContainer() {
std::vector<int*> v;
v.push_back(new int(10)); // 向容器中添加动态分配的内存
// 忘记释放容器中的内存,导致内存泄漏
}

// ---------------- tips ----------------
void memoryLeakWithMallocAndDelete() {
int* p = (int*)malloc(sizeof(int)); // 使用 malloc 分配内存
delete p; // 错误,应该使用 free 而不是 delete
}

// ---------------- tips ----------------
void memoryLeakWithArray() {
int* arr = new int[5]; // 使用 new[] 分配数组
// 错误:使用 delete 释放数组
delete arr; // 错误,应该使用 delete[] 而不是 delete
}

// ---------------- tips ----------------
struct MyStruct {
int id;
std::vector<int> values;
};

void memoryLeakWithMemset() {
MyStruct s;
s.id = 10;
s.values = {1, 2, 3};

std::cout << "Before memset: id = " << s.id << ", values.size() = " << s.values.size() << std::endl;

// 错误:使用 memset 重置结构体,可能导致内存泄漏或未定义行为
memset(&s, 0, sizeof(s));


MyStruct s2;
// 错误:使用 memcpy 复制结构体,直接拷贝 STL 容器会破坏其内部状态
memcpy(&s2, &s1, sizeof(MyStruct));

std::cout << "After memset: id = " << s.id << ", values.size() = " << s.values.size() << std::endl;
}

// ---------------- tips ----------------
void memoryLeakWithSharedPtr() {
std::shared_ptr<MyStruct> s1 = std::make_shared<MyStruct>();
s1->p = std::make_shared<int>(10);

// 错误:如果没有正确管理 shared_ptr,可能会发生循环引用,导致内存泄漏
std::shared_ptr<MyStruct> s2 = s1;
// shared_ptr 循环引用,无法释放内存
}

// ---------------- tips ----------------
void memoryLeakWithKVReplace() {
std::map<int, int*> test_map;
test_map[1] = new int(1);
test_map[1] = new int(2); // 替换了之前key=1的value,导致上一行内存泄漏
}

// ---------------- tips ----------------
void test() {
std::vector<int> vec;
for(int i = 0; i < 100000000000000; ++i) {
vec.push_back(i); // 错误逻辑导致的内存膨胀(内存泄漏都查完,内存还是暴涨,往这个方向查)
}
}
------ 本文结束 ------
------ 版权声明:转载请注明出处 ------