文章目录
- 1. 概述
- 1. 体系结构
- 2. Linux 程序内存空间布局
- 3. 内存检查原理
- 2. valgrind工具
- 3. 常用选项
- 4. 示例
- 1. 内存泄漏
- 2. 数组越界
- 3. 内存覆盖
- 4. 使用未初始化的值
- 5. 内存申请与释放函数不匹配
- 5. 总结
1. 概述
1. 体系结构
Valgrind 是一套 Linux 下,开放源代码(GPL V2)的仿真调试工具的集合。
Valgrind模拟了一个 CPU 环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。
体系结构如下图所示:
2. Linux 程序内存空间布局
要发现Linux下的内存问题,首先要知道在Linux下,内存是如何被分配的?Linux C程序内存空间布局如下:
- 代码段(.text):存放CPU要执行的指令。代码段是可共享的,相同的代码在内存中只会有一个拷贝,同时这个段是只读的。
- 初始化数据段(.data):存放的是程序中需要明确赋初始值的变量,例如位于所有函数之外的全局变量。
- 未初始化数据段(.bss):位于这一段中的数据,内核在执行该程序前,将其初始化为0或者null。例如出现在任何函数之外的全局变量:int sum;
- 堆(Heap):这个段用于在程序中进行动态内存申请,例如经常用到的malloc,new系列函数就是从这个段中申请内存。
- 栈(Stack):函数中的局部变量以及在函数调用过程中产生的临时变量都保存在此段中。
3. 内存检查原理
Memcheck检测内存问题的原理如下图所示:
Memcheck 能够检测出内存问题,关键在于其建立了两个全局表:
- Valid-Value 表:
对于进程的整个地址空间中的每一个字节(byte),都有与之对应的 8 个 bits;对于 CPU 的每个寄存器,也有一个与之对应的 bit 向量。这些 bits 负责记录该字节或者寄存器值是否具有有效的、已初始化的值。 - Valid-Address 表
对于进程整个地址空间中的每一个字节(byte),还有与之对应的 1 个 bit,负责记录该地址是否能够被读写。
检测原理:
- 当要读写内存中某个字节时,首先检查这个字节对应的 A bit。如果该A bit显示该位置是无效位置,memcheck 则报告读写错误。
- 内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit 也被加载到虚拟的 CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。
2. valgrind工具
- Memcheck,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:
- 使用未初始化的内存;
- 使用已经释放了的内存;
- 内存访问越界等。
- Callgrind,用来检查程序中函数调用过程中出现的问题。
- Cachegrind,用来检查程序中缓存使用出现的问题。
- Helgrind,用来检查多线程程序中出现的竞争问题。
- Massif,用来检查程序中堆栈使用中出现的问题。
- Extension,可以利用core提供的功能,自己编写特定的内存调试工具。
# centos 中安装
sudo yum install valgrind valgrind-devel valgrind-tools# 查看是否安装成功
valgrind --version
3. 常用选项
- –tool=[default: memcheck]:运行 toolname 指定的工具,默认 memcheck,还可以为cachegrid、drd、lackey、callgrind、helgrind、massif等;
- –leak-check=no | summary | full:对发现的内存泄露给出的信息级别,只有memcheck可用。
- –trace-childer=no | yes [default: no]:跟踪子线程;
- –log-file=:输出Log信息到指定的文件;
- –time-stamp=no | yes [default: no]:增加时间戳到 Log 信息;
- –xml=yes:将错误信息以xml格式输出,只有memcheck可用;
- –xml-file=:XML输出到指定文件;
- –error-limit=no | yes:如果错误太多,则停止显示新错误;
- –error-exitcode=:如果发现错误,则返回错误代码;
- –num-callers=(num):这个值默认是12,最高是50。表示显示多少层的堆栈,设置越高会使Valgrind运行越慢而且使用更多的内存,但是在嵌套调用层次比较高的程序中非常实用。
- –track-fds=no | yes:跟踪打开的文件描述;
- –show-reachable=no | yes:用于控制是否检测控制范围之外的泄漏,比如全局指针、static指针等;
- –trace-children=no | yes [default: no]:是否跟入子进程;
- -h, --help:显示帮助信息;
- –version:显示 valgrind 版本;
- -q, --quiet:安静地运行,只打印错误信息
- -v, --verbose:更详细的信息,增加错误数统计。
4. 示例
1. 内存泄漏
示例代码:
// memleak_demo.cpp
#include <iostream>int main() {int *p = new int;*p = 123;return 0;
}
编译程序时,需要加上-g选项。
g++ -g -o demo memleak_demo.cpp
执行结果(加参数 -v):
# --tool=memcheck:使用Memcheck这个工具进行内存检查
# --leak-check=full:表示开启全面的内存泄漏检查,该选项会告诉Valgrind输出详情并报告所有未释放的情况。
# -v:获得更详细的信息。
#
-->$ valgrind --tool=memcheck --leak-check=full -v ./demo
==11763== Memcheck, a memory error detector
==11763== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==11763== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==11763== Command: ./demo
==11763==
--11763-- Valgrind options:
--11763-- --tool=memcheck
--11763-- --leak-check=full
--11763-- -v
--11763-- Contents of /proc/version:
--11763-- Linux version 2.6.32-642.6.2.el6.x86_64 (mockbuild@worker1.bsys.centos.org) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC) ) #1 SMP Wed Oct 26 06:52:09 UTC 2016
--11763-- Arch and hwcaps: AMD64, amd64-sse3-cx16-lzcnt-avx2-bmi
--11763-- Page sizes: currently 4096, max supported 4096
--11763-- Valgrind library directory: /usr/lib64/valgrind
--11763-- Reading syms from /data/neilnie/mypro/cp/linuxperf/valgrind/demo
--11763-- Reading syms from /lib64/ld-2.12.so
--11763-- Reading syms from /usr/lib64/valgrind/memcheck-amd64-linux
--11763-- object doesn't have a dynamic symbol table
--11763-- Scheduler: using generic scheduler lock implementation.
--11763-- Reading suppressions file: /usr/lib64/valgrind/default.supp
==11763== embedded gdbserver: reading from /tmp/vgdb-pipe-from-vgdb-to-11763-by-neilnie-on-QC_GZ-172_24_19_228-neil_dev
==11763== embedded gdbserver: writing to /tmp/vgdb-pipe-to-vgdb-from-11763-by-neilnie-on-QC_GZ-172_24_19_228-neil_dev
==11763== embedded gdbserver: shared mem /tmp/vgdb-pipe-shared-mem-vgdb-11763-by-neilnie-on-QC_GZ-172_24_19_228-neil_dev
==11763==
==11763== TO CONTROL THIS PROCESS USING vgdb (which you probably
==11763== don't want to do, unless you know exactly what you're doing,
==11763== or are doing some strange experiment):
==11763== /usr/lib64/valgrind/../../bin/vgdb --pid=11763 ...command...
==11763==
==11763== TO DEBUG THIS PROCESS USING GDB: start GDB like this
==11763== /path/to/gdb ./demo
==11763== and then give GDB the following command
==11763== target remote | /usr/lib64/valgrind/../../bin/vgdb --pid=11763
==11763== --pid is optional if only one valgrind process is running
==11763==
--11763-- REDIR: 0x4017c30 (strlen) redirected to 0x38049551 (vgPlain_amd64_linux_REDIR_FOR_strlen)
--11763-- Reading syms from /usr/lib64/valgrind/vgpreload_core-amd64-linux.so
--11763-- Reading syms from /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so
--11763-- REDIR: 0x4017a40 (index) redirected to 0x4c28c30 (index)
--11763-- REDIR: 0x4017ac0 (strcmp) redirected to 0x4c29570 (strcmp)
--11763-- Reading syms from /usr/lib64/libstdc++.so.6.0.13
--11763-- object doesn't have a symbol table
--11763-- Reading syms from /lib64/libm-2.12.so
--11763-- Reading syms from /lib64/libgcc_s-4.4.7-20120601.so.1
--11763-- object doesn't have a symbol table
--11763-- Reading syms from /lib64/libc-2.12.so
--11763-- REDIR: 0x5653d00 (strcasecmp) redirected to 0x4a2255c (_vgnU_ifunc_wrapper)
--11763-- REDIR: 0x5655fc0 (strncasecmp) redirected to 0x4a2255c (_vgnU_ifunc_wrapper)
--11763-- REDIR: 0x5651c70 (__GI_strrchr) redirected to 0x4c28ab0 (__GI_strrchr)
--11763-- REDIR: 0x5650190 (__GI_strlen) redirected to 0x4c28fb0 (__GI_strlen)
--11763-- REDIR: 0x564e710 (strcmp) redirected to 0x4a2255c (_vgnU_ifunc_wrapper)
--11763-- REDIR: 0x564e750 (__GI_strcmp) redirected to 0x4c29520 (__GI_strcmp)
--11763-- REDIR: 0x4eec0b0 (operator new(unsigned long)) redirected to 0x4c2857a (operator new(unsigned long))
--11763-- REDIR: 0x564a920 (free) redirected to 0x4c273a9 (free)
==11763==
==11763== HEAP SUMMARY:
==11763== in use at exit: 4 bytes in 1 blocks
==11763== total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==11763==
==11763== Searching for pointers to 1 not-freed blocks
==11763== Checked 179,320 bytes
==11763==
==11763== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==11763== at 0x4C285FC: operator new(unsigned long) (vg_replace_malloc.c:298)
==11763== by 0x400741: main (memleak_demo.cpp:5)
==11763==
==11763== LEAK SUMMARY:
==11763== definitely lost: 4 bytes in 1 blocks
==11763== indirectly lost: 0 bytes in 0 blocks
==11763== possibly lost: 0 bytes in 0 blocks
==11763== still reachable: 0 bytes in 0 blocks
==11763== suppressed: 0 bytes in 0 blocks
==11763==
==11763== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)
--11763--
--11763-- used_suppression: 4 U1004-ARM-_dl_relocate_object
--11763-- used_suppression: 2 glibc-2.5.x-on-SUSE-10.2-(PPC)-2a
==11763==
==11763== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)
执行结果(不加参数 -v):
-->$ valgrind --tool=memcheck --leak-check=full ./demo
==12383== Memcheck, a memory error detector
==12383== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==12383== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==12383== Command: ./demo
==12383==
==12383==
==12383== HEAP SUMMARY:
==12383== in use at exit: 4 bytes in 1 blocks
==12383== total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==12383==
==12383== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12383== at 0x4C285FC: operator new(unsigned long) (vg_replace_malloc.c:298)
==12383== by 0x400741: main (memleak_demo.cpp:5)
==12383==
==12383== LEAK SUMMARY:
==12383== definitely lost: 4 bytes in 1 blocks
==12383== indirectly lost: 0 bytes in 0 blocks
==12383== possibly lost: 0 bytes in 0 blocks
==12383== still reachable: 0 bytes in 0 blocks
==12383== suppressed: 0 bytes in 0 blocks
==12383==
==12383== For counts of detected and suppressed errors, rerun with: -v
==12383== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)
definitely lost:内存泄漏,另外可以看到有4个字节的内存泄漏。
2. 数组越界
示例1 代码:
// memleak_demo.cpp
#include <iostream>int main() {int arr[10] = {0};std::cout << arr[10] << std::endl;return 0;
}
执行结果:
-->$ g++ -g -o demo memleak_demo.cpp
-->$ valgrind --tool=memcheck --leak-check=full ./demo
==15091== Memcheck, a memory error detector
==15091== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==15091== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==15091== Command: ./demo
==15091==
==15091== Conditional jump or move depends on uninitialised value(s)
==15091== at 0x4EB0B96: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EB0E25: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EC443D: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x40087E: main (memleak_demo.cpp:6)
==15091==
==15091== Use of uninitialised value of size 8
==15091== at 0x4EAC6B3: ??? (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EB0BC2: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EB0E25: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EC443D: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x40087E: main (memleak_demo.cpp:6)
==15091==
==15091== Conditional jump or move depends on uninitialised value(s)
==15091== at 0x4EAC6BE: ??? (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EB0BC2: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EB0E25: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EC443D: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x40087E: main (memleak_demo.cpp:6)
==15091==
==15091== Conditional jump or move depends on uninitialised value(s)
==15091== at 0x4EB0BF8: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EB0E25: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x4EC443D: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib64/libstdc++.so.6.0.13)
==15091== by 0x40087E: main (memleak_demo.cpp:6)
==15091==
0
==15091==
==15091== HEAP SUMMARY:
==15091== in use at exit: 0 bytes in 0 blocks
==15091== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==15091==
==15091== All heap blocks were freed -- no leaks are possible
==15091==
==15091== For counts of detected and suppressed errors, rerun with: -v
==15091== Use --track-origins=yes to see where uninitialised values come from
==15091== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 6 from 6)
IConditional jump or move depends on uninitialised value(s) 访问了未初始化的内存(访问数组越界)。
示例2 代码:
// memleak_demo.cpp
#include <vector>
#include <iostream>int main() {std::vector<int> v(10, 0);std::cout << v[10] << std::endl;return 0;
}
执行结果:
-->$ g++ -g -o demo memleak_demo.cpp
-->$ valgrind --tool=memcheck --leak-check=full ./demo
==19508== Memcheck, a memory error detector
==19508== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==19508== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==19508== Command: ./demo
==19508==
==19508== Invalid read of size 4
==19508== at 0x400ABF: main (memleak_demo.cpp:7)
==19508== Address 0x5963068 is 0 bytes after a block of size 40 alloc'd
==19508== at 0x4C285FC: operator new(unsigned long) (vg_replace_malloc.c:298)
==19508== by 0x400FAB: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:104)
==19508== by 0x400EA8: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (in /data/neilnie/mypro/cp/linuxperf/valgrind/demo)
==19508== by 0x400D86: void std::vector<int, std::allocator<int> >::_M_initialize_dispatch<int>(int, int, std::__true_type) (stl_vector.h:1163)
==19508== by 0x400BF0: std::vector<int, std::allocator<int> >::vector<int>(int, int, std::allocator<int> const&) (stl_vector.h:404)
==19508== by 0x400AA1: main (memleak_demo.cpp:6)
==19508==
0
==19508==
==19508== HEAP SUMMARY:
==19508== in use at exit: 0 bytes in 0 blocks
==19508== total heap usage: 1 allocs, 1 frees, 40 bytes allocated
==19508==
==19508== All heap blocks were freed -- no leaks are possible
==19508==
==19508== For counts of detected and suppressed errors, rerun with: -v
==19508== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)
Invalid read of size 4 表示越界读取 4 个字节,这个操作出现在 memleak_demo.cpp 文件的第 7 行。另外可以看到,vector 分配了一块 40 字节的内存,程序越界访问这块内存之后的 4 个字节。
3. 内存覆盖
示例:
// memleak_demo.cpp
#include <string.h>
#include <stdio.h>
#include <stdlib.h>int main() {char x[10];int i;for (i=0;i<10;i++) {x[i] = i+1;}strncpy(x+3,x,4);for (i=0;i<10;i++) {printf("x(%d) = %d)\n", i, x[i]);}return 0;
}
执行结果:
-->$ g++ -g -o demo memleak_demo.cpp
-->$ valgrind --tool=memcheck --leak-check=full ./demo
==9862== Memcheck, a memory error detector
==9862== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==9862== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==9862== Command: ./demo
==9862==
==9862== Source and destination overlap in strncpy(0x7ff0003a7, 0x7ff0003a4, 4)
==9862== at 0x4C2937F: __GI_strncpy (mc_replace_strmem.c:477)
==9862== by 0x400687: main (memleak_demo.cpp:13)
==9862==
x(0) = 1)
x(1) = 2)
x(2) = 3)
x(3) = 1)
x(4) = 2)
x(5) = 3)
x(6) = 1)
x(7) = 8)
x(8) = 9)
x(9) = 10)
==9862==
==9862== HEAP SUMMARY:
==9862== in use at exit: 0 bytes in 0 blocks
==9862== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==9862==
==9862== All heap blocks were freed -- no leaks are possible
==9862==
==9862== For counts of detected and suppressed errors, rerun with: -v
==9862== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)
Source and destination overlap in strncpy,13号代码strncpy发生内存覆盖。
4. 使用未初始化的值
示例:
// memleak_demo.cpp
#include <iostream>int main() {int n;if (n == 0) {std::cout << "n is zero" << std::endl;}return 0;
}
执行结果:
-->$ g++ -g -o demo memleak_demo.cpp
-->$ valgrind --tool=memcheck --leak-check=full ./demo
==16762== Memcheck, a memory error detector
==16762== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==16762== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==16762== Command: ./demo
==16762==
==16762== Conditional jump or move depends on uninitialised value(s)
==16762== at 0x40087C: main (memleak_demo.cpp:6)
==16762==
n is zero
==16762==
==16762== HEAP SUMMARY:
==16762== in use at exit: 0 bytes in 0 blocks
==16762== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==16762==
==16762== All heap blocks were freed -- no leaks are possible
==16762==
==16762== For counts of detected and suppressed errors, rerun with: -v
==16762== Use --track-origins=yes to see where uninitialised values come from
==16762== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)
uninitialised value(s) ,memleak_demo.cpp第6行使用了未初始化内存。
5. 内存申请与释放函数不匹配
示例代码:
// memleak_demo.cpp
#include <stdlib.h>
#include <iostream>int main() {int *p = NULL;p = (int*)malloc(sizeof(int));if (p == NULL) {std::cout<<"malloc failed"<<std::endl;}std::cout<<"address [0x%p]"<<std::endl;delete p;return 0;
}
执行结果:
-->$ g++ -g -o demo memleak_demo.cpp
-->$ valgrind --tool=memcheck --leak-check=full ./demo
==476== Memcheck, a memory error detector
==476== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==476== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==476== Command: ./demo
==476==
address [0x%p]
==476== Mismatched free() / delete / delete []
==476== at 0x4C27016: operator delete(void*) (vg_replace_malloc.c:480)
==476== by 0x400978: main (memleak_demo.cpp:12)
==476== Address 0x5963040 is 0 bytes inside a block of size 4 alloc'd
==476== at 0x4C27A2E: malloc (vg_replace_malloc.c:270)
==476== by 0x400929: main (memleak_demo.cpp:7)
==476==
==476==
==476== HEAP SUMMARY:
==476== in use at exit: 0 bytes in 0 blocks
==476== total heap usage: 1 allocs, 1 frees, 4 bytes allocated
==476==
==476== All heap blocks were freed -- no leaks are possible
==476==
==476== For counts of detected and suppressed errors, rerun with: -v
==476== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)
在 C++ 中,内存分配和释放规则:
- 如果使用 malloc、calloc、realloc、valloc 或 memalign 分配,则必须使用 free 释放。
- 如果使用 new 分配,则必须使用 delete 释放。
- 如果使用 new[] 分配,则必须使用 delete[] 释放。
5. 总结
valgrind是一款非常强大的内存泄漏检测工具,在我们的项目和学习中有很大的作用,尤其是从事C/C++开发人员。
相关文献:
https://valgrind.org/
https://man7.org/linux/man-pages/man1/valgrind.1.html