内存泄漏检测工具Valgrind与OpenCV搭配使用

内存泄漏的含义

关于”内存泄漏“,有两种定义:

  1. 程序结束时,有指针变量ptr能访问到申请的堆内存,但没通过free(ptr)来释放。即“程序结束时知道它在哪里,但确实没释放它”。

  2. 程序结束时,之前申请的堆内存无法被任何一个指针变量访问,导致无法访问这块堆内存空间,造成了泄漏。也就是“程序结束时不知道它在哪里,也就没办法释放它”。

Valgrind的Memcheck工具,融合了上述两种类型的定义,并细分为如下5种:

编号 内存泄漏类型 具体含义 说明
1 definitely lost 很明显的内存泄漏 必须修复
2 indirectly lost 程序的一个基于指针的结构里存在内存泄漏 必须修复
3 possibly lost 全局或静态指针指向堆内存,指针往后偏移但仍在该内存内,并最终free 必须修复
4 still reachable 全局或静态指针指向堆内存,最终没有free这块内存 通常不严重,可以不管
5 suppressed 被抑制的内存错误,基于传入的suppressed文件 通常是配置忽略第三方库的内存泄漏

容易想到,让OpenCV搭配Valgrind,检查和报告其内存泄漏:

valgrind-opencv.png

我遇到的情况是OpenCV有内存泄漏,通过传入opencv.supp文件可以抑制它产生的possibly lost内存错误(放到了suppressed类型中),still reachable错误则忽略不管。生成和使用opencv.supp文件:

valgrind --gen-suppressions=all --leak-check=yes ./a.out 2>&1 | tee valgrind.out

sed '/==/ d' valgrind.out > opencv.supp

valgrind --leak-check=yes --suppressions=opencv.supp ./a.out

以下是探索(罗嗦)版本的内容:

Valgrind基本介绍

在Linux下用Valgrind检查自己代码的内存泄漏情况。Valgrind是一组工具的集合,其中最基本的、并且是默认的工具,是Memcheck,可以检查内存泄漏情况。

Valgrind是一组工具的集合:

  1. Memcheck:内存错误检查器。最主要的、默认的工具(不需要指定--tool=memcheck就会使用的)
  2. 其他工具:Cachegrind / Callgrind / Helgrind / DTR / Massif / DHAT / SGcheck / BBV / ...,这些工具需要用--tool=xxx参数指定后才能使用

Valgrind支持的平台有很多:

X86/Linux, AMD64/Linux, ARM/Linux, ARM64/Linux, PPC32/Linux, PPC64/Linux, PPC64LE/Linux, S390/Linux, MIPS32/Linux, MIPS64/Linux, X86/Solaris, AMD64/Solaris, ARM/Android(2.3.x and later), ARM64/Android, X86/Android(4.0 and later), MIPS32/Android, X86/Darwin, AMD64/Darwin

使用Valgrind的Memcheck工具,基本步骤

  1. 编写自己的程序;
vim xxx.c
  1. 使用-g参数编译程序,用来提供行号信息、符号表等;
gcc xxx.c -g
  1. 使用valgrind调用第2步编译出的可执行程序,建议添加--leak-check=yes参数来获取详细信息
valgrind --leak-check=yes ./a.out [arg1, arg2, ...]

其中[arg1, arg2, ...]表示可选参数

Valgrind的输出格式简要说明

比如我的测试代码是:

#include <stdlib.h>

void f(void)
{
    int* x = malloc(10 * sizeof(int));
    x[10] = 0;        // problem 1: heap block overrun(越界)
}                    // problem 2: memory leak -- x not freed(内存泄漏)

int main(void)
{
    f();
    return 0;
}

对应的Valgrind输出为:

==5738== Invalid write of size 4
==5738== at 0x400544: f (test.c:6)
==5738== by 0x400555: main (test.c:11)
==5738== Address 0x5204068 is 0 bytes after a block of size 40 alloc'd
==5738== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5738== by 0x400537: f (test.c:5)
==5738== by 0x400555: main (test.c:11)

说明:

  • 每条错误信息中都有很多细节,仔细看
  • 5738是进程编号(pid),通常来说不重要
  • 第一行("Invalid write...") 表明了错误类型。这里的情况是,写到申请的堆内存(heap block)之外了,也就是越界。
  • 接下来的几行是栈记录(stack trace),表明了问题出在哪里。栈记录可能会很多,甚至让人看得头疼,尤其是当你用了C++ STL的时候。这些信息从下往上看会好一点。如果栈记录不大,可以使用--num-callers参数让它多数出东西。
  • 代码地址(比如0x400544)通常来说不重要,但有时候对于跟踪诡异的bug来说很重要。
  • 一些重要的错误信息有第二个组件,描述了相关的内存地址。在这个例子中显示的是,被写的内存地址刚刚超过了代码中第5行malloc()分配的内存块的最后面。

通常是从最前面的错误开始修复,因为后面的出错很可能是前面的错误导致的。不然的化你会觉得Memcheck很难用。

Valgrind的Memcheck工具的内存泄漏错误类型举例

在valgrind的输出内容中,找到LEAK SUMMARY字段,它下面汇总了各种类型的内存泄漏:

内存泄漏细分类型 含义
definitely lost 确信无疑的内存泄漏
indirectly lost 间接的内存泄漏
possibly lost 可能有内存泄漏
still reachable 程序退出时仍然可以访问的内存,也是泄漏
suppressed 被抑制的

(表格参考:Valgrind笔记(一)——Memcheck初探 )

在我这样一个小白看来,细分成这么多类型的内存泄漏,我其实不是很分得清楚各自的区别。能否举例说明呢?经过一番搜索,基于这篇博客:valgrind 所报的4种内存丢失到底是什么意思(简洁),我拆分和修改出以上4种类型的内存泄漏各自的例子,根据例子更容易理解。

例子0:definitely lost

definitely lost意思是:确信无疑的内存泄漏,缺少free/detelete/delete[]操作。
举例:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    float* data = (float*)malloc(sizeof(float)*5);

    return 0;
}
gcc example2.c -g 
valgrind --leak-check=yes ./a.out

==23257== Memcheck, a memory error detector
==23257== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==23257== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==23257== Command: ./a.out
==23257==
==23257==
==23257== HEAP SUMMARY:
==23257== in use at exit: 20 bytes in 1 blocks
==23257== total heap usage: 1 allocs, 0 frees, 20 bytes allocated
==23257==
==23257== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==23257== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==23257== by 0x400577: main (example2.c:6)
==23257==
==23257== LEAK SUMMARY:
==23257== definitely lost: 20 bytes in 1 blocks
==23257== indirectly lost: 0 bytes in 0 blocks
==23257== possibly lost: 0 bytes in 0 blocks
==23257== still reachable: 0 bytes in 0 blocks
==23257== suppressed: 0 bytes in 0 blocks
==23257==
==23257== For counts of detected and suppressed errors, rerun with: -v
==23257== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

例子1:indirectly lost

indirectly lost,说的很玄乎,还是看例子比较直观:

#include <stdio.h>
#include <stdlib.h>

typedef struct Image {
    int h, w, c;
    float* data;
} Image;

int main()
{
    Image* im = (Image*)malloc(sizeof(Image));
    int h = 224, w = 224, c = 3;
    im->h = h;
    im->w = w;
    im->c = c;
    im->data = (float*)malloc(sizeof(float)*h*w*c);

    return 0;
}
gcc example2.c -g
valgrind --leak-check=yes ./a.out

==23580== Memcheck, a memory error detector
==23580== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==23580== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==23580== Command: ./a.out
==23580==
==23580==
==23580== HEAP SUMMARY:
==23580== in use at exit: 602,136 bytes in 2 blocks
==23580== total heap usage: 2 allocs, 0 frees, 602,136 bytes allocated
==23580==
==23580== 602,136 (24 direct, 602,112 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==23580== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==23580== by 0x400537: main (example2.c:11)
==23580==
==23580== LEAK SUMMARY:
==23580== definitely lost: 24 bytes in 1 blocks
==23580== indirectly lost: 602,112 bytes in 1 blocks
==23580== possibly lost: 0 bytes in 0 blocks
==23580== still reachable: 0 bytes in 0 blocks
==23580== suppressed: 0 bytes in 0 blocks
==23580==
==23580== For counts of detected and suppressed errors, rerun with: -v
==23580== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

样例代码中,缺少的是:

free(im->data);
free(im);

因为没有free(im),因此im对应的内存泄漏了;而im对应的内存块里面,有一个data字段,data字段现在也没有被释放,因此,602,112字节说的是224*224*4*sizeof(float)这么一大块内存是indirectly lost。需要注意的是,definitely lost此时为24字节,如果调用了free(im)也就是消除了当前的definitely lost,则当前的indirectly lost会升级为definitely lost

例子2:possibly lost

找到possibly lost的例子并不很容易。按官方说法是,指针指向malloc申请的内存,然后指针往后++,再free这个指针,就得到possibly lost。看看下面这两个例子,按照这个解释,其实分别得到possibly lost和definitely lost。

possibly lost:

#include <stdio.h>
#include <stdlib.h>

int* g_p1;

void fun()
{
    g_p1 = (int*)malloc(sizeof(int)*10);
    g_p1++;
}

int main()
{
    fun();
    free(g_p1);

    return 0;
}

==24652== Memcheck, a memory error detector
==24652== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==24652== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==24652== Command: ./a.out
==24652==
==24652== Invalid free() / delete / delete[] / realloc()
==24652== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==24652== by 0x4005AC: main (example2.c:15)
==24652== Address 0x5204044 is 4 bytes inside a block of size 40 alloc'd
==24652== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==24652== by 0x400573: fun (example2.c:8)
==24652== by 0x40059D: main (example2.c:14)
==24652==
==24652==
==24652== HEAP SUMMARY:
==24652== in use at exit: 40 bytes in 1 blocks
==24652== total heap usage: 1 allocs, 1 frees, 40 bytes allocated
==24652==
==24652== 40 bytes in 1 blocks are possibly lost in loss record 1 of 1
==24652== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==24652== by 0x400573: fun (example2.c:8)
==24652== by 0x40059D: main (example2.c:14)
==24652==
==24652== LEAK SUMMARY:
==24652== definitely lost: 0 bytes in 0 blocks
==24652== indirectly lost: 0 bytes in 0 blocks
==24652== possibly lost: 40 bytes in 1 blocks
==24652== still reachable: 0 bytes in 0 blocks
==24652== suppressed: 0 bytes in 0 blocks
==24652==
==24652== For counts of detected and suppressed errors, rerun with: -v
==24652== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

definitely lost:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int* p1 = (int*)malloc(sizeof(int)*10);
    p1++;
    free(p1);

    return 0;
}

==24703== Memcheck, a memory error detector
==24703== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==24703== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==24703== Command: ./a.out
==24703==
==24703== Invalid free() / delete / delete[] / realloc()
==24703== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==24703== by 0x40058C: main (example22.c:8)
==24703== Address 0x5204044 is 4 bytes inside a block of size 40 alloc'd
==24703== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==24703== by 0x400577: main (example22.c:6)
==24703==
==24703==
==24703== HEAP SUMMARY:
==24703== in use at exit: 40 bytes in 1 blocks
==24703== total heap usage: 1 allocs, 1 frees, 40 bytes allocated
==24703==
==24703== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==24703== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==24703== by 0x400577: main (example22.c:6)
==24703==
==24703== LEAK SUMMARY:
==24703== definitely lost: 40 bytes in 1 blocks
==24703== indirectly lost: 0 bytes in 0 blocks
==24703== possibly lost: 0 bytes in 0 blocks
==24703== still reachable: 0 bytes in 0 blocks
==24703== suppressed: 0 bytes in 0 blocks
==24703==
==24703== For counts of detected and suppressed errors, rerun with: -v
==24703== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

例子3:still reachable

和例子2类似,still reacheable类型的内存泄漏,和definitely lost类型的内存泄漏,我觉得仍然是一个是全局变量的指针、另一个是局部变量指针的区别。来看例子:

still reachable

#include <stdio.h>
#include <stdlib.h>

int* g_p1;

void fun()
{
    g_p1 = (int*)malloc(sizeof(int)*10);
}

int main()
{
    fun();

    return 0;
}

==24892== Memcheck, a memory error detector
==24892== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==24892== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==24892== Command: ./a.out
==24892==
==24892==
==24892== HEAP SUMMARY:
==24892== in use at exit: 40 bytes in 1 blocks
==24892== total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==24892==
==24892== LEAK SUMMARY:
==24892== definitely lost: 0 bytes in 0 blocks
==24892== indirectly lost: 0 bytes in 0 blocks
==24892== possibly lost: 0 bytes in 0 blocks
==24892== still reachable: 40 bytes in 1 blocks
==24892== suppressed: 0 bytes in 0 blocks
==24892== Reachable blocks (those to which a pointer was found) are not shown.
==24892== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==24892==
==24892== For counts of detected and suppressed errors, rerun with: -v
==24892== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

definitely lost
其实这个例子就是“例子0:definitely lost”的例子,2333:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int* g_p1 = (int*)malloc(sizeof(int)*10);

    return 0;
}

==24984== Memcheck, a memory error detector
==24984== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==24984== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==24984== Command: ./a.out
==24984==
==24984==
==24984== HEAP SUMMARY:
==24984== in use at exit: 40 bytes in 1 blocks
==24984== total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==24984==
==24984== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==24984== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==24984== by 0x400537: main (example33.c:6)
==24984==
==24984== LEAK SUMMARY:
==24984== definitely lost: 40 bytes in 1 blocks
==24984== indirectly lost: 0 bytes in 0 blocks
==24984== possibly lost: 0 bytes in 0 blocks
==24984== still reachable: 0 bytes in 0 blocks
==24984== suppressed: 0 bytes in 0 blocks
==24984==
==24984== For counts of detected and suppressed errors, rerun with: -v
==24984== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

简要总结Memcheck的内存泄漏类型

  1. definitely lost: 局部变量,用了malloc/calloc申请的堆内存,没有释放
  2. indirectly lost: 某个局部变量结构体对象本身是从malloc/calloc申请的堆内存,没有释放;并且,这个结构体的某个成员是个指针,也是用malloc/calloc从堆上申请的内存,也没有释放。此时,这个成员指向的内存,是indirectly lost。如果这个结构体对象被释放了,那么这个成员指向的内存,就是definitely lost。
  3. possibly lost:全局变量,指针类型,在某个函数中用malloc/calloc申请了堆内存给它,然后指针往后偏移了一部分,但是仍然处于这块堆内存里面,并且最后free了这个指针。那么这是possibly lost。
  4. still reachable:全局变量,指针类型,在某个函数中用malloc/calloc申请了堆内存给它,并且这个指针没有偏移;但是呢,最终程序没有free掉这个指针。这就是still reachable。

不管是以上哪种类型的内存泄漏,最理想最安全的做法都应该是,修复其中每种错误,确保没有内存泄漏。然而事实上也许并不容易,因为一旦你的程序用了第三方库,而这个第三方库有内存泄漏,那就需要搞清楚是谁写了bug。典型的例子是OpenCV。

OpenCV内存泄漏,例子1

测试环境:ubuntu16.04

opencv版本:sudo apt install libopencv-dev,2.4.9

#include <stdio.h>
#include <stdlib.h>

#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/highgui/highgui_c.h>

int main()
{
    const char* filename = "cat.jpg";
    IplImage* im = cvLoadImage(filename, -1);
    cvReleaseImage(&im);

    return 0;
}

Makefile:

CC=clang-3.5
OPENCV=opencv #apt-get's opencv
#OPENCV=/usr/local/opencv-4.1.1/lib/pkgconfig/opencv4.pc

#CFLAGS=-Wall `pkg-config --cflags $(OPENCV)` -O3 -flto -ffast-math
CFLAGS=-Wall `pkg-config --cflags $(OPENCV)` -O0 -g
LDFLAGS=`pkg-config --libs $(OPENCV)` -lm
#VPATH=./src/

OBJ=example4.o

all: a.out

a.out: $(OBJ)
    $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

.PHONY: clean

clean:
    rm -rf $(OBJ) a.out

编译运行:如果开启--leak-check=yes,输出内容特别多,这里没有开启

make
valgrind ./a.out

输出:

==26429== Memcheck, a memory error detector
==26429== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==26429== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==26429== Command: ./a.out
==26429==
==26429==
==26429== HEAP SUMMARY:
==26429== in use at exit: 228,088 bytes in 931 blocks
==26429== total heap usage: 4,282 allocs, 3,351 frees, 2,267,105 bytes allocated
==26429==
==26429== LEAK SUMMARY:
==26429== definitely lost: 0 bytes in 0 blocks
==26429== indirectly lost: 0 bytes in 0 blocks
==26429== possibly lost: 1,352 bytes in 18 blocks
==26429== still reachable: 226,736 bytes in 913 blocks
==26429== of which reachable via heuristic:
==26429== newarray : 1,536 bytes in 16 blocks
==26429== suppressed: 0 bytes in 0 blocks
==26429== Rerun with --leak-check=full to see details of leaked memory
==26429==
==26429== For counts of detected and suppressed errors, rerun with: -v
==26429== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

看到以上输出中的"possibly lost"和"still reachable"后,显然是OpenCV源码中有内存泄漏,至于在哪里并不清楚。

尝试切换opencv版本,从2.4.9切换到自行编译的3.4.5,依然是上述代码,valgrind输出为:

==27765== Memcheck, a memory error detector
==27765== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==27765== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==27765== Command: ./a.out
==27765==
./a.out: error while loading shared libraries: libopencv_dnn.so.3.4: cannot open shared object file: No such file or directory
==27765== Jump to the invalid address stated on the next line
==27765== at 0x566: ???
==27765== by 0x401044C: _dl_signal_error (dl-error.c:125)
==27765== by 0x400F069: _dl_map_object_deps (dl-deps.c:686)
==27765== by 0x4003A28: dl_main (rtld.c:1647)
==27765== by 0x40198AC: _dl_sysdep_start (dl-sysdep.c:249)
==27765== by 0x4001C29: _dl_start_final (rtld.c:323)
==27765== by 0x4001C29: _dl_start (rtld.c:429)
==27765== by 0x4000C37: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
==27765== Address 0x566 is not stack'd, malloc'd or (recently) free'd
==27765==
==27765==
==27765== Process terminating with default action of signal 11 (SIGSEGV)
==27765== Bad permissions for mapped region at address 0x566
==27765== at 0x566: ???
==27765== by 0x401044C: _dl_signal_error (dl-error.c:125)
==27765== by 0x400F069: _dl_map_object_deps (dl-deps.c:686)
==27765== by 0x4003A28: dl_main (rtld.c:1647)
==27765== by 0x40198AC: _dl_sysdep_start (dl-sysdep.c:249)
==27765== by 0x4001C29: _dl_start_final (rtld.c:323)
==27765== by 0x4001C29: _dl_start (rtld.c:429)
==27765== by 0x4000C37: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
==27765==
==27765== HEAP SUMMARY:
==27765== in use at exit: 0 bytes in 0 blocks
==27765== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==27765==
==27765== All heap blocks were freed -- no leaks are possible
==27765==
==27765== For counts of detected and suppressed errors, rerun with: -v
==27765== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

看起来,opencv3.4.5相比于opencv2.4.9,减少一些内存泄漏呀。

OpenCV内存泄漏,例子2

这次的例子,不仅仅读取和释放图像,还show图像(基于opencv3.4.5):

#include <stdio.h>
#include <stdlib.h>

#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/highgui/highgui_c.h>

int main()
{
    const char* filename = "cat.jpg";
    IplImage* im = cvLoadImage(filename, -1);
    cvShowImage("cat", im);
    cvReleaseImage(&im);
    cvWaitKey(0);

    return 0;
}

valgrind输出:

==28062== Memcheck, a memory error detector
==28062== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28062== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28062== Command: ./a.out
==28062==
==28062==
==28062== HEAP SUMMARY:
==28062== in use at exit: 1,726,832 bytes in 16,877 blocks
==28062== total heap usage: 100,861 allocs, 83,984 frees, 8,410,822 bytes allocated
==28062==
==28062== LEAK SUMMARY:
==28062== definitely lost: 0 bytes in 0 blocks
==28062== indirectly lost: 0 bytes in 0 blocks
==28062== possibly lost: 4,740 bytes in 50 blocks
==28062== still reachable: 1,631,380 bytes in 16,084 blocks
==28062== of which reachable via heuristic:
==28062== length64 : 5,376 bytes in 87 blocks
==28062== newarray : 2,096 bytes in 51 blocks
==28062== suppressed: 0 bytes in 0 blocks
==28062== Rerun with --leak-check=full to see details of leaked memory
==28062==
==28062== For counts of detected and suppressed errors, rerun with: -v
==28062== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

看到如上输出,看来OpenCV的内存泄漏问题还是没有完全解决。

配置Valgrind,忽略OpenCV的内存泄漏

不得不说,自行去翻看Valgrind中配置memcheck的suppression的文档感觉很繁琐...懒人做法是,让valgrind自己输出需要supprress的每一个配置,重定向到文件,然后去掉额外没用的打印输出内容,保存为opencv-3.4.5.supp文件,下次给valgrind用,就可以忽略opencv的内存泄漏了。

#输出内存错误检测结果,并夹带suppression的配置一并输出;重定向到文件
valgrind --gen-suppressions=all --leak-check=yes ./a.out 2>&1 | tee valgrind.out

#去除文件中不必要的信息,并重定向到新的文件中
sed '/==/ d' valgrind.out > opencv-3.4.5.supp

(参考:Ignore part of code with valgrind - memcheck)

对应的代码:(没错,代码里仅仅是include了opencv,别的啥都没做,但是valgrind就是能检测到内存泄漏):

#include <stdio.h>
#include <stdlib.h>

#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/highgui/highgui_c.h>

int main()
{

    return 0;
}

没有配置suppression文件的valgrind输出:

valgrind --leak-check=yes ./a.out

==6715== LEAK SUMMARY:
==6715== definitely lost: 0 bytes in 0 blocks
==6715== indirectly lost: 0 bytes in 0 blocks
==6715== possibly lost: 1,352 bytes in 18 blocks
==6715== still reachable: 237,176 bytes in 1,206 blocks
==6715== of which reachable via heuristic:
==6715== newarray : 1,536 bytes in 16 blocks
==6715== suppressed: 0 bytes in 0 blocks
==6715== Reachable blocks (those to which a pointer was found) are not shown.
==6715== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==6715==
==6715== For counts of detected and suppressed errors, rerun with: -v
==6715== ERROR SUMMARY: 18 errors from 18 contexts (suppressed: 0 from 0)

配置了suppression的valgrind输出:

valgrind --leak-check=yes --suppressions=opencv-3.4.5.supp ./a.out

==6746== HEAP SUMMARY:
==6746== in use at exit: 238,528 bytes in 1,224 blocks
==6746== total heap usage: 6,218 allocs, 4,994 frees, 666,594 bytes allocated
==6746==
==6746== LEAK SUMMARY:
==6746== definitely lost: 0 bytes in 0 blocks
==6746== indirectly lost: 0 bytes in 0 blocks
==6746== possibly lost: 0 bytes in 0 blocks
==6746== still reachable: 237,176 bytes in 1,206 blocks
==6746== of which reachable via heuristic:
==6746== newarray : 1,536 bytes in 16 blocks
==6746== suppressed: 1,352 bytes in 18 blocks
==6746== Reachable blocks (those to which a pointer was found) are not shown.
==6746== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==6746==
==6746== For counts of detected and suppressed errors, rerun with: -v
==6746== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 18 from 18)

没错,possibily lost从1352字节减少到0字节,总算把一部分内存泄漏的锅甩给OpenCV了!当然,still reachable没啥变化。。

这里贴一下我导出的opencv的suppression配置文件的内容(opencv-3.4.5.supp):

{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   fun:g_slice_alloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_add_interface_static
   fun:gtk_button_get_type
   fun:gtk_toggle_button_get_type
   fun:gtk_module_init
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   fun:g_slice_alloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_add_interface_static
   fun:gtk_icon_view_get_type
   fun:gtk_module_init
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   fun:g_slice_alloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_add_interface_static
   fun:gtk_icon_view_get_type
   fun:gtk_module_init
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   fun:g_type_class_ref
   fun:g_type_class_ref
   fun:g_param_spec_flags
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   fun:g_type_class_ref
   fun:gtk_module_init
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   fun:g_closure_invoke
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_signal_emit_valist
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   fun:g_type_class_ref
   fun:g_type_class_ref
   fun:g_param_spec_enum
   obj:/usr/lib/x86_64-linux-gnu/libgdk-3.so.0.1800.9
   fun:g_type_class_ref
   fun:g_type_class_ref
   fun:g_object_new_valist
   fun:g_object_new
   obj:/usr/lib/x86_64-linux-gnu/libgdk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgdk-3.so.0.1800.9
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   fun:g_slice_alloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_add_interface_static
   fun:gtk_button_get_type
   fun:gtk_toggle_button_get_type
   fun:gtk_module_init
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_static
   fun:g_flags_register_static
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   fun:g_type_class_ref
   fun:g_object_new_valist
   fun:g_object_new
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   fun:g_type_class_ref
   fun:g_type_class_ref
   fun:g_type_create_instance
   fun:g_param_spec_internal
   fun:g_param_spec_object
   obj:/usr/lib/x86_64-linux-gnu/libgdk-3.so.0.1800.9
   fun:g_type_class_ref
   fun:g_object_newv
   fun:g_object_new
   fun:gdk_display_manager_get
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_object_new_valist
   fun:g_object_new
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:g_malloc0
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_fundamental
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_object_new_valist
   fun:g_object_new
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_static
   fun:g_param_type_register_static
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:call_init.part.0
   fun:call_init
   fun:_dl_init
   obj:/lib/x86_64-linux-gnu/ld-2.23.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   fun:g_memdup
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   fun:gtk_style_context_set_state
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_object_new_valist
   fun:g_object_new
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:malloc
   fun:g_malloc
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_object_new_valist
   fun:g_object_new
   obj:/usr/lib/x86_64-linux-gnu/libgtk-3.so.0.1800.9
   fun:gtk_style_new
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_static
   fun:g_enum_register_static
   fun:atk_state_type_get_type
   fun:atk_state_type_get_name
   fun:atk_object_notify_state_change
   fun:gtk_accessible_set_widget
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_object_new_valist
   fun:g_object_new
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:allocate_dtv
   fun:_dl_allocate_tls
   fun:allocate_stack
   fun:pthread_create@@GLIBC_2.2.5
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
   fun:g_thread_new
   obj:/usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so
   obj:/usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so
   obj:/usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_object_new_valist
   fun:g_object_new
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:allocate_dtv
   fun:_dl_allocate_tls
   fun:allocate_stack
   fun:pthread_create@@GLIBC_2.2.5
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
   fun:g_thread_new
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
   fun:g_task_get_type
   obj:/usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.4800.2
   fun:g_bus_get_sync
   obj:/usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:allocate_dtv
   fun:_dl_allocate_tls
   fun:allocate_stack
   fun:pthread_create@@GLIBC_2.2.5
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
   fun:g_thread_new
   obj:/usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.4800.2
   obj:/usr/lib/x86_64-linux-gnu/libgio-2.0.so.0.4800.2
   fun:g_bus_get_sync
   obj:/usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so
   obj:/usr/lib/x86_64-linux-gnu/gio/modules/libdconfsettings.so
   fun:g_main_context_dispatch
}
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: possible
   fun:realloc
   fun:g_realloc
   obj:/usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.4800.2
   fun:g_type_register_static
   fun:g_type_register_static_simple
   fun:gtk_window_group_get_type
   fun:gtk_window_group_new
   fun:gtk_window_get_group
   fun:gtk_main_do_event
   obj:/usr/lib/x86_64-linux-gnu/libgdk-3.so.0.1800.9
   fun:g_main_context_dispatch
   obj:/lib/x86_64-linux-gnu/libglib-2.0.so.0.4800.2
}

那么,still reachable到底什么意思呢?

参考http://shigaro.org/2019/07/05/valgrind-2/#

There is more than one way to define “memory leak”. In particular, there are two primary definitions of “memory leak” that are in common usage among programmers.

The first commonly used definition of “memory leak” is, “Memory was allocated and was not subsequently freed before the program terminated.” However, many programmers (rightly) argue that certain types of memory leaks that fit this definition don’t actually pose any sort of problem, and therefore should not be considered true “memory leaks”.

An arguably stricter (and more useful) definition of “memory leak” is, “Memory was allocated and cannot be subsequently freed because the program no longer has any pointers to the allocated memory block.” In other words, you cannot free memory that you no longer have any pointers to. Such memory is therefore a “memory leak”. Valgrind uses this stricter definition of the term “memory leak”. This is the type of leak which can potentially cause significant heap depletion, especially for long lived processes.

The “still reachable” category within Valgrind’s leak report refers to allocations that fit only the first definition of “memory leak”. These blocks were not freed, but they could have been freed (if the programmer had wanted to) because the program still was keeping track of pointers to those memory blocks.

In general, there is no need to worry about “still reachable” blocks. They don’t pose the sort of problem that true memory leaks can cause. For instance, there is normally no potential for heap exhaustion from “still reachable” blocks. This is because these blocks are usually one-time allocations, references to which are kept throughout the duration of the process’s lifetime. While you could go through and ensure that your program frees all allocated memory, there is usually no practical benefit from doing so since the operating system will reclaim all of the process’s memory after the process terminates, anyway. Contrast this with true memory leaks which, if left unfixed, could cause a process to run out of memory if left running long enough, or will simply cause a process to consume far more memory than is necessary.

翻译为:

定义”内存泄漏”的方式不止一种. 特别的, 在程序员间常用的主要有两种”内存泄漏”的定义.

第一种常用的定义是, “内存被分配, 随后没有在程序结束前被释放”. 但是, 很多程序员(正确地)主张说符合这一定义的内存泄漏并不会造成问题, 因此并不被认为是真正的内存泄漏.

“内存泄漏”的一种可能更为严格(也更有用)的定义是, “内存被分配后, 由于程序失去了指向被分配内存块的指针而无法被释放”. 换句话说, 你无法释放没有指针指向的内存. 所以这样的内存属于”内存泄漏”. Valgrind用的是这一更为严格的定义. 这类泄漏可能产生严重的堆损耗, 特别是在长期活动的进程中.

Valgrind的泄漏报告中”still reachable”分类指的是只满足第一类定义的内存分配. 这些内存块没有被释放, 但他们是可以被释放的(只要程序员愿意), 因为程序仍然保有指向这些内存块的指针.

一般而言, 不必担心”still reachable”的内存块. 他们不会带来真正的内存泄漏会导致的问题. 比如说, “still reachable”的内存块通常不会导致堆耗尽. 这是因为这些块都是单次分配, 程序在整个生命周期中都保留对他们的指向. 你当然可以梳理整个程序, 保证这些内存块都被释放, 但这实际并没什么好处, 因为操作系统会在进程结束后回收进程的全部内存. 与之相对, 如果真正的内存泄漏没有被修正, 那么就会导致一个进程在运行足够长时间后耗尽所有内存, 或者说消耗比它所必需的多得多的内存.

看来,still reachable类型的内存泄漏,意思是“本该释放的堆内存,并且一直有指针能够访问这些堆内存,但是到程序结束的时候,都没有释放掉”。

上面这大段的引用(英文原文),其实来在https://stackoverflow.com/questions/3840582/still-reachable-leak-detected-by-valgrind,这个SO的问题回答和评论中,还有人提到still reachable的一个特例:FILE指针。没错,当打开一个文件的时候,用了FILE* fp,无论是读取还是写入,如果程序最终没有关闭这个fp,valgrind会把它算作“still reachable”。。。。其实这个说法也不严禁。经过实验我发现,在缺少fclose(fp)的情况下:

  • 如果是FILE* fp = fopen("in.txt", "r")(读取文件)

    • 文件"in.txt"存在,那么会有still reachable问题
    • 不存在"in.txt",则没有still reachable问题
  • 如果是FILE* fp=fclose("in.txt", "w")(写入文件),则存在still reachable问题(内存不足的极端情况没有测试,2333)

显然,上述关于文件读写的情况,是因为其内部实现中有使用缓冲区来存储需要读取或写入的内容,这个缓冲区应该是从堆上动态分配的内存空间,可以参考APUE(《unix环境高级编程)上的相关描述,例如这篇博客:fopen的默认缓冲大小和setvbuf 用法

此外,这个SO回答下,还有人给出了still reachable的更精确定义:

Here is a proper explanation of "still reachable":

"Still reachable" are leaks assigned to global and static-local variables. Because valgrind tracks global and static variables it can exclude memory allocations that are assigned "once-and-forget". A global variable assigned an allocation once and never reassigned that allocation is typically not a "leak" in the sense that it does not grow indefinitely. It is still a leak in the strict sense, but can usually be ignored unless you are pedantic.

Local variables that are assigned allocations and not free'd are almost always leaks.

Here is an example

int foo(void)
{
    static char *working_buf = NULL;
    char *temp_buf;
    if (!working_buf) {
         working_buf = (char *) malloc(16 * 1024);
    }
    temp_buf = (char *) malloc(5 * 1024);

    ....
    ....
    ....

}

Valgrind will report working_buf as "still reachable - 16k" and temp_buf as "definitely lost - 5k".

个人比较认同这个回答中的解释,"Still reachable" are leaks assigned to global and static-local variables(Still reachable对应的内存泄漏是说,全局或者static局部变量的指针,对应的内存泄漏)。

使用OpenCV官方的suppression配置文件来忽略内存泄漏

搞了半天发现自己民科了...opencv的github issue中早有人提出了类似“opencv你怎么在valgrind下有内存泄漏”的问题:OpenCV 3.3.0 memory leak for CLAHE Ubuntu 16.04,官方issue发言人也早有了回答:

alalek commented on 21 Sep 2017

Consider to use valgrind suppression files from here:

https://github.com/opencv/opencv/tree/master/platforms/scripts

果然官方的supp文件好用!粘贴opencv.supp如下:

{
   OpenCV-IPP static init
   Memcheck:Cond
   fun:ippicvGetCpuFeatures
   fun:ippicvStaticInit
}

{
   OpenCV-getInitializationMutex
   Memcheck:Leak
   ...
   fun:_ZN2cv22getInitializationMutexEv
}

{
   OpenCV-SingletonBuffer
   Memcheck:Leak
   ...
   fun:_ZN2cv20allocSingletonBufferEm
}

{
   OpenCV-SingletonNewBuffer
   Memcheck:Leak
   ...
   fun:_ZN2cv23allocSingletonNewBufferEm
}

{
   OpenCV-getStdAllocator
   Memcheck:Leak
   ...
   fun:_ZN2cv3Mat15getStdAllocatorEv
}

{
   OpenCV-getOpenCLAllocator
   Memcheck:Leak
   ...
   fun:_ZN2cv3ocl18getOpenCLAllocatorEv
}

{
   OpenCV-getCoreTlsData
   Memcheck:Leak
   fun:_Znwm
   fun:_ZN2cv14getCoreTlsDataEv
}

{
   OpenCV-TLS-getTlsStorage
   Memcheck:Leak
   ...
   fun:_ZN2cvL13getTlsStorageEv
}

{
   OpenCV-TLS-getData()
   Memcheck:Leak
   ...
   fun:*setData*
   fun:_ZNK2cv16TLSDataContainer7getDataEv
}

{
   OpenCV-parallel_for-reconfigure
   Memcheck:Leak
   ...
   fun:_ZN2cv10ThreadPool12reconfigure_Ej
}

{
   OpenCV-parallel_for-instance
   Memcheck:Leak
   fun:_Znwm
   fun:*instance*
   ...
   fun:_ZN2cv13parallel_for_ERKNS_5RangeERKNS_16ParallelLoopBodyEd
}

{
   OpenCV-parallel_for-setNumThreads()
   Memcheck:Leak
   ...
   fun:_ZN2cv13setNumThreadsEi
}

{
   OpenCV-parallel_for-getNumThreads()
   Memcheck:Leak
   ...
   fun:_ZN2cv13getNumThreadsEv
}

{
   OpenCV-getIPPSingelton
   Memcheck:Leak
   ...
   fun:_ZN2cv3ippL15getIPPSingeltonEv
}

{
   OpenCV-getGlobalMatOpInitializer
   Memcheck:Leak
   fun:_Znwm
   fun:_ZN2cvL25getGlobalMatOpInitializerEv
}

{
   OpenCV-CoreTLSData
   Memcheck:Leak
   ...
   fun:_ZNK2cv7TLSDataINS_11CoreTLSDataEE3getEv
}

{
   OpenCV-getThreadID()
   Memcheck:Leak
   ...
   fun:_ZN2cv5utils11getThreadIDEv
}

{
   OpenCV-ThreadID
   Memcheck:Leak
   fun:_Znwm
   fun:_ZNK2cv7TLSDataINS_12_GLOBAL__N_18ThreadIDEE18createDataInstanceEv
}

{
   OpenCV-ThreadID-TLS
   Memcheck:Leak
   fun:_Znwm
   fun:getThreadIDTLS
}

{
   OpenCV-CoreTLS
   Memcheck:Leak
   fun:_Znwm
   fun:_ZNK2cv7TLSDataINS_11CoreTLSDataEE18createDataInstanceEv
}

{
   OpenCV-UMatDataAutoLockerTLS
   Memcheck:Leak
   ...
   fun:_ZN2cvL21getUMatDataAutoLockerEv
}

{
   OpenCV-haveOpenCL
   Memcheck:Leak
   ...
   fun:_ZN2cv3ocl10haveOpenCLEv
}

{
   OpenCV-DNN-getLayerFactoryMutex
   Memcheck:Leak
   ...
   fun:_ZN2cv3dnn*L20getLayerFactoryMutexEv
}

{
   OpenCV-ocl::Context
   Memcheck:Leak
   ...
   fun:_ZN2cv3ocl7Context10getDefaultEb
}

{
   OpenCV-ocl::Device
   Memcheck:Leak
   ...
   fun:_ZN2cv3ocl6Device10getDefaultEv
}

{
   OpenCV-ocl::Queue
   Memcheck:Leak
   ...
   fun:_ZN2cv3ocl5Queue6createERKNS0_7ContextERKNS0_6DeviceE
}

{
   OpenCV-ocl::Program
   Memcheck:Leak
   ...
   fun:_ZN2cv3ocl6Kernel6createEPKcRKNS0_7ProgramE
}

{
   OpenCV-ocl::ProgramEntry
   Memcheck:Leak
   ...
   fun:_ZNK2cv3ocl8internal12ProgramEntrycvRNS0_13ProgramSourceEEv
}

{
   OpenCV-ocl::Context::getProg
   Memcheck:Leak
   ...
   fun:_ZN2cv3ocl7Context7getProgERKNS0_13ProgramSourceERKNS_6StringERS5_
}

{
   OpenCV-getTraceManager()
   Memcheck:Leak
   ...
   fun:getTraceManagerCallOnce
}

{
   OpenCV-ITT
   Memcheck:Leak
   ...
   fun:__itt_*create*
}

{
   OpenCV-gtk_init
   Memcheck:Leak
   ...
   fun:gtk_init
   fun:cvInitSystem
}

{
   OpenCV-FFmpeg-swsscale
   Memcheck:Addr16
   ...
   fun:sws_scale
   fun:_ZN20CvVideoWriter_FFMPEG10writeFrameEPKhiiiii
   fun:cvWriteFrame_FFMPEG
}

{
   OpenCV-GStreamer-gst_init
   Memcheck:Leak
   ...
   fun:gst_init
}

{
   OpenCV-GStreamer-gst_deinit
   Memcheck:Leak
   ...
   fun:gst_deinit
}

{
   OpenCV-GStreamer-gst_init_check
   Memcheck:Leak
   ...
   fun:gst_init_check
}

{
   OpenCV-GStreamer-gst_parse_launch_full-reachable
   Memcheck:Leak
   match-leak-kinds: reachable
   ...
   fun:gst_parse_launch_full
}

{
   OpenCV-OpenEXR-ThreadPool
   Memcheck:Leak
   fun:_Znwm
   fun:_ZN16IlmThread_opencv10ThreadPoolC1Ej
   fun:_ZN16IlmThread_opencv10ThreadPool16globalThreadPoolEv
}

{
   OpenCV-test-gapi-thread-tls
   Memcheck:Leak
   match-leak-kinds: possible
   fun:calloc
   fun:allocate_dtv
   fun:_dl_allocate_tls
}

试试看?测试代码,仅仅是include了opencv而不做任何事情的:

#include <stdio.h>
#include <stdlib.h>

#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/highgui/highgui_c.h>

int main()
{
    return 0;
}

结果输出:

==8700== LEAK SUMMARY:
==8700== definitely lost: 0 bytes in 0 blocks
==8700== indirectly lost: 0 bytes in 0 blocks
==8700== possibly lost: 1,352 bytes in 18 blocks
==8700== still reachable: 237,120 bytes in 1,204 blocks
==8700== of which reachable via heuristic:
==8700== newarray : 1,536 bytes in 16 blocks
==8700== suppressed: 56 bytes in 2 blocks
==8700== Reachable blocks (those to which a pointer was found) are not shown.
==8700== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==8700==
==8700== For counts of detected and suppressed errors, rerun with: -v
==8700== ERROR SUMMARY: 18 errors from 18 contexts (suppressed: 0 from 0)

仍然看到possibly lost,并不完美。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容