C++不用工具,如何检测内存泄漏?

2021-07-12  本文已影响0人  DayDayUpppppp

在知乎上面看到一个很有意思的问题,“C++不用工具,如何检测内存泄漏?”。年入百万的知乎科学家大概有四个思路,一种思路是重载operator new,一种思路是hook malloc,另外一种是使用内存池。

关于内存管理的一些前置背景知识:
https://github.com/zhaozhengcoder/CoderNoteBook/blob/master/note/c++%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86.md

1. 重载operator new

在C++中operator new是可以被重载的,一般有两种重载的方式,全局重载和在某个类内重载,这两种的区别就不在这里展开写的。通过重载一个全局的operator new的函数,在new的时候附加上参数文件名和行号。然后,定义一个新的宏,在new的时候带上申请内存的文件名和行号。这样当业务代码new的时候,会调用重载的operator new函数,这样就获得了申请内存的代码文件名和行号,甚至都可以带上backtrace做更加精确的定位。

// libc中的定义
void* operator new(std::size_t sz)

// 定义一个宏,给new附带上两个参数 文件名和行号
#define new new(__LINE__,__FILE__)

// 重载operator new
void* operator new (size_t size , const char *file , 
        unsigned int line ) {
    if (void *ptr = malloc (size)) 
    {
        // print backtrace();
        cout << endl << "new : " << file << " "<< line << endl ;
        return ptr ;
    }  
    throw std::bad_alloc () ;
}

// 重新实现operator delete (void *ptr) 
// 由于c++lib中operator delete (void *ptr) 是一个弱符号的函数,所以可以重新实现一个,且不会出现重定义
void operator delete (void *ptr) 
{
    if (ptr == nullptr) return ;

    cout << ptr << "\tsource has been released !" << endl ;
    free (ptr);
    ptr = nullptr;
}

这种方案会两个小问题:

  1. new的宏定义只有include这个宏的地方才会生效,对于库函数或者无法include宏的地方就没有办法插入log。
  2. 只能记录内存是被哪些逻辑分配出去的,并没有非常准确的定位到是那块内存泄漏了,需要根据申请内存的日志去进一步分析。(比如一些通用的内存信息,有非常多的业务逻辑都会调用申请,那么知道了是这个类型的内存泄露只能缩小问题的范围,并没有办法准确定位)。

对于问题1,可以重新实现operator new来解决。因为libc中默认的operator new是weak符号,可以重新实现一个strong符号去覆盖。对于没有include宏的地方,也可以进入void * operator new (size_t size)的函数中。

void * operator new (size_t size) 
{ 
    cout << endl << "new size : " << size << endl ;
    void *p = malloc(size); 
    return p;
} 

int main()
{
    {
        int * q1 = new int();
        delete q1;
    }
    {
        // 底层的内存分配算法也会走重新实现的operator new函数
        shared_ptr<A> a = make_shared<A>();
    }
}

对于问题2,见到过有个很有意思的实现方式,可以在分配内存的时候,额外多申请一块(head + 实际的内容),head中记录申请内存的文件名和行号,然后将head插入到一个全局的list中。在free的时候,根据free的指针,ptr-sizeof(head) 找到head的头部。把head从全局的list中释放掉。当程序结束的时候,全局的list中就维护了申请但是还没有释放的内存信息。执行效果如下:

自己实现了一个乞丐版:
https://github.com/zhaozhengcoder/CoderNoteBook/tree/master/example_code/memleak_check/mem_leak_tool


2. hook malloc

malloc函数和operator new不一样的是,它并不是一个weak的符号(但在glibc下是一个弱符号),没有办法想new一样,重新实现一个去覆盖。因此,会看到很多hook malloc的方案,常见的有:

  1. 重新实现一个malloc函数,通过preload 动态库的方式去覆盖默认的malloc函数;
  2. 另一种思路是使用 wrap 编译参数(好像也叫编译垫片)

关于什么是弱符号和强符号,正常情况下我们定义的函数都是强符号,这样如果定义了两个相同强符号,就会出现重定义的报错。因此,很多库函数的实现思路是,定义为弱符号,这样链接的时候,如果出现了同名的强符号,就会被覆盖,而不是报错。关于什么是强符号和弱符号

  1. 使用编译器选项wrap重载malloc函数

ld链接选项wrap定义

按照文档的说法,加上wrap的选项之后,调用malloc的时候,会调用wrap的版本。而系统的malloc被重命名为real_malloc,可以通过调用real_malloc来调用系统malloc函数

extern "C" void * __real_malloc(size_t size);
extern "C" void __real_free(void *ptr);

extern "C" void * __wrap_malloc(size_t size) {
    printf("my malloc: %zu\n", size);
    return __real_malloc(size);
}

extern "C" void __wrap_free(void *ptr) {
    printf("my free: %p\n", ptr);
    __real_free(ptr);
}

// g++ main.cpp test1.cpp test2.cpp memory_op.cpp -Wl,-wrap=malloc -Wl,-wrap=free

自己实现了一个乞丐版:
https://github.com/zhaozhengcoder/CoderNoteBook/tree/master/example_code/memleak_check/mem_hook

3. 引入内存池解决

业务层实现内存池来进行分配,这样内存分配上面业务层有更高的权限,也可以做更多的事情来掌控内存的分配。比如定义malloc(size, type) ,业务层分配内存的时候,带上一个type,这样就很容易监控到某某type分配了多少块。
http://www.almostinfinite.com/memtrack.html

4. 使用工具

内存泄漏检测工具
valgrind、ASan、mtrace、ccmalloc、debug_new

对于内存方面的常见错误,valgrind还是很好使用的。

// 内存泄漏
int mem_leak()
{
    int * p = new int;
    int * arr = new int[100];
    return 0;
}

// 内存访问越界
int mem_out_of_boundary()
{
    const int size = 10;
    int * arr = new int[size];

    for (int i = 0; i <= size; i++)
    {
        arr[i] = i;
    }
    return 0;
}

// free后再次访问
int mem_use_after_free()
{
    const int size = 10;
    int * arr = new int[size];

    delete[] arr;

    arr[0] = 100;
    return 0;
}

// 多次free
int mem_invalid_free()
{
    const int size = 10;
    int * arr = new int[size];
    int * p = arr;
    delete arr;
    delete p;
    return 0;
}

# valgrind --tool=memcheck ./a.out

其他补充:

  1. 强符号和弱符号的区别

  2. 两个静态库or动态库包含同样的符号,链接会怎么样
    https://zhuanlan.zhihu.com/p/352668522
    https://blog.csdn.net/Solstice/article/details/6423342

(写不动了,后面再说吧。。

上一篇下一篇

猜你喜欢

热点阅读