【问题标题】:valgrind leak error after deletion of all dynamic memory删除所有动态内存后valgrind泄漏错误
【发布时间】:2021-11-29 07:04:38
【问题描述】:

我尝试学习 CPP 指针以及释放我使用过的所有内存 valgrind。但不幸的是,我遇到了泄漏错误,我不知道我在哪里犯了错误。也没有太多关于从 valgrind 中找到错误的想法,而是一种人类可读的方式。任何发现泄漏的指导都是非常可观的。

编译器: g++ (Ubuntu 7.5.0-3ubuntu1~16.04) 7.5.0

sn-p相关信息

  • smart pointer 不是故意使用的
  • 这是一个最小的示例代码。所以,有些部分似乎是不必要的。

file.cpp

#include <iostream>

void algo_fun(unsigned int*  ip_ptr_array_,
              unsigned int   ip_size_,
              unsigned int** op_ptr_array_,
              unsigned int*  op_size_)
{
    *(op_size_) = ip_size_ + 2;

    // following approach is good as it allocate dynamic memory
    unsigned int* local = new unsigned int[*(op_size_)];

    for (unsigned int i = 0; i< *(op_size_); i++)
    {
        local[i]=i+1*3;
    }

    *op_ptr_array_ = &local[0];
    local[3] = 87;
}

int main()
{
    // input array's contetnt
    unsigned int ip_size = 10;
    unsigned int* ip_ptr_array = new unsigned int[ip_size];

    // output data
    unsigned int op_size;
    unsigned int* op_ptr_array;

    // filling input array
    for(unsigned int i = 0; i < ip_size; i++)
    {
        ip_ptr_array[i] = i+2*2;
    }

    // function calling to get output data
    algo_fun(ip_ptr_array,
             ip_size,
             &op_ptr_array,
             &op_size);

    delete [] ip_ptr_array;
    delete [] op_ptr_array;

    return 0;
}

可以在here找到工作版本。

用于测试的命令: valgrind --leak-check=full --show-leak-kinds=all -v ./file

Leak Summary from valgrind

==23138== LEAK SUMMARY:
==23138==    definitely lost: 0 bytes in 0 blocks
==23138==    indirectly lost: 0 bytes in 0 blocks
==23138==      possibly lost: 0 bytes in 0 blocks
==23138==    still reachable: 72,704 bytes in 1 blocks
==23138==         suppressed: 0 bytes in 0 blocks
==23138== 
==23138== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==23138== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

【问题讨论】:

  • 您为什么要手动使用newdelete?这就是 C++ 中容器和智能指针的用途。
  • 无法重现,valgrid 打印“所有堆块都已释放——不可能有泄漏”。
  • algo_fun(ip_ptr_array, ip_size,&amp;op_ptr_array,&amp;op_size); -- 最后一个参数不需要是指针。它应该只是一个普通的int。如果您查看algo_fun 函数,它绝对不会暗示它应该是一个指针——例如,在该函数中没有进行nullptr 检查。看起来你对代码中的所有内容都进行了指针化。
  • @PaulMcKenzie 据我了解,ip_ 表示输入参数op_ 表示输出参数algo_fun() 将输出数组的元素个数存储到 op_size_ 指针指向的位置,参见:*(op_size_) = ip_size_ + 2; 因此,它需要是一个指针。至于指针化一切,我认为作者只是在学习/练习他们关于 C++ 指针的知识。否则,根本不需要在任何地方使用指针,只需 std::vectors 和引用。我认为这段代码只是一个概念证明。例如,ip_ptr_array_ 参数未使用。
  • @πάνταῥεῖ 故意避开smart pointer

标签: c++ pointers memory-leaks valgrind


【解决方案1】:

tl;dr 请检查您使用的是最新的 Valgrind。

结合使用 Valgrind 和 gdb,您应该能够看到发生了什么。

我得到以下信息(FreeBSD 12.2、g++ 10.3.0、从 git HEAD 构建的 Valgrind,[操作系统和编译器版本不相关])。我正在使用--trace-malloc=yes 选项来查看所有的 mallloc/free 调用。不要在大型​​应用程序上这样做。

$ valgrind --leak-check=full --trace-malloc=yes ./test

--61886-- malloc(72704) = 0x5800040
--61886-- calloc(1984,1) = 0x5811C80
--61886-- calloc(104,1) = 0x5812480
--61886-- calloc(224,1) = 0x5812530
--61886-- calloc(80,1) = 0x5812650
--61886-- calloc(520,1) = 0x58126E0
--61886-- calloc(88,1) = 0x5812930
--61886-- _Znam(40) = 0x58129D0
--61886-- _Znam(48) = 0x5812A40
--61886-- _ZdaPv(0x58129D0)
--61886-- _ZdaPv(0x5812A40)
--61886-- free(0x5800040)
==61886== 
==61886== HEAP SUMMARY:
==61886==     in use at exit: 3,000 bytes in 6 blocks
==61886==   total heap usage: 9 allocs, 3 frees, 75,792 bytes allocated

_Znam 是数组 new 的修改版本,_ZdaPv 是代码中数组 delete 的修改版本。对 malloc/calloc/free 的其他调用来自 libc/libstc++。您可能会在 Linux 或 libc++ 上看到不同的痕迹。

您可以使用--show-reachable=yes 获取有关可访问内存的信息。

如果我现在在 gdb 下运行。

$ gdb ./test
(gdb) b malloc
(gdb) b malloc
(gdb) r
Breakpoint 1, malloc (nbytes=96) at /usr/src/libexec/rtld-elf/rtld.c:5877

这是对链接加载器 ld.so 中的 malloc 的调用。然后我又执行了几个 'r's 直到

Breakpoint 1, __je_malloc_initialized () at jemalloc_jemalloc.c:208

这里的链接加载器已经加载了 libstdc++.so 并且全局 malloc 已经被 jemalloc 替换了。

获取调用栈

(gdb) bt
#0  __je_malloc_initialized () at jemalloc_jemalloc.c:208
#1  imalloc (sopts=<optimized out>, dopts=<optimized out>) at jemalloc_jemalloc.c:1990
#2  __malloc (size=72704) at jemalloc_jemalloc.c:2042
#3  0x00000008006eb6f4 in ?? () from /usr/local/lib/gcc10/libstdc++.so.6
#4  0x000000080060e2fd in objlist_call_init (list=<optimized out>, lockstate=<optimized out>) at /usr/src/libexec/rtld-elf/rtld.c:2820
#5  0x000000080060d03d in _rtld (sp=0x7fffffffe408, exit_proc=0x7fffffffe3d0, objp=0x7fffffffe3d8) at /usr/src/libexec/rtld-elf/rtld.c:811
#6  0x000000080060a8c9 in rtld_start () at /usr/src/libexec/rtld-elf/amd64/rtld_start.S:39
#7  0x0000000000000000 in ?? ()
(gdb) 

注意#2 行上的相同分配大小。

我不打算深入研究这个内存的用途,它是 C++ 运行时的一部分。

libstdc++(和 libc)故意不释放该内存(可能这很困难或不值得)。为了过滤掉这些分配,Valgrind 使用了一个特殊的函数来请求 libstc++/libc 释放这块内存。

这种情况下的选项是

    --run-cxx-freeres=no|yes  free up libstdc++ memory at exit on Linux

(严格来说,不仅仅是 Linux)。

查看 Valgrind 发行说明

Release 3.12.0 (20 October 2016)
...
* New option --run-cxx-freeres=<yes|no> can be used to change whether
  __gnu_cxx::__freeres() cleanup function is called or not. Default is
  'yes'.

所以我假设您使用的是 3.11 或更早版本。

最后,如果我使用 clang++/libc++,则没有运行时分配(也没有 freeres 函数)。

【讨论】:

  • 是的,是版本问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 1970-01-01
  • 1970-01-01
  • 2012-05-22
  • 2020-03-31
  • 2016-03-15
  • 2014-04-09
相关资源
最近更新 更多