乐趣区

关于c++11:CC实用工具内存相关问题排查工具cppcheck与valgrind

C++ 中令人纳闷的内存问题

C++ 的内存问题时常令人非常困惑。总结起来 C ++ 的内存问题能够分为以下几类

  1. 内存泄露
    当程序员应用 new(或 malloc)关键字分配内存而遗记应用 delete(或 free)函数或 delete[] 运算符开释内存时,C++ 中就会产生内存透露。在 C++ 中应用谬误的删除运算符会产生内存透露最多的状况之一。delete 运算符利用于开释单个调配的内存空间,而 delete [] 运算符利用于开释数据值数组。内存透露对于很多不能停机的程序是致命的。它会导致内存使用量就一直减少,直至程序宕机。
  2. 内存越界
    内存越界的背地其实是拜访异样的内存地位。内存越界导致的问题往往让 C ++ 程序员非常困惑的问题,因为由内存越界导致程序呈现 crash 的地位往往不是真正导致程序 crash 的地位,这给排查内存越界带来很大的艰难。
  3. 拜访未初始化对象
    与内存越界相似,拜访未初始化的对象往往也会导致程序在其余地位 crash。而产生 crash 的地位往往并不是导致 crash 起因。

对于一个谨严的程序员是绝不能让内存拜访问题带到正式公布版本的。因而须要一些工具来帮忙咱们排查内存相干问题。本文的示例代码为内存透露。

动态查看工具 —Cppcheck

CppCheck 是一个 C /C++ 代码缺点动态查看工具。不同于 C /C++ 编译器及其它剖析工具,CppCheck 只查看编译器查看不进去的 bug,不查看语法错误。所谓动态代码查看就是应用一个工具查看咱们写的代码是否平安和强壮,是否有暗藏的问题。

Cppcheck 的装置

在 Ubuntu 下只须要运行 sudo apt install cppcheck 即可装置。装置胜利后,通过 cppcheck --version 命令,能够查看是否装置胜利,以及查看版本。作者装置的版本是 1.82。

Cppcheck 的应用

cppcheck 的应用也非常简略对于项查看的文件只须要输出 cppcheck ${filename} 即可。
例如在内存透露示例中,对 main.cpp 进行动态代码查看,会发现如下问题:

~/Code/leak_example$ cppcheck main.cpp 
cppcheck main.cpp 
Checking main.cpp ...
[main.cpp:13]: (error) Array 'b[10]' accessed at index 99, which is out of bounds.
[main.cpp:35]: (error) Memory leak: a

会发现在 main.cpp 第 13 行,呈现了数组越界的问题,同时会发现代码中没有回收在开始时调配的内存。对于源代码扩散在多个目录下的我的项目,能够间接把根目录作为 cppcheck 命令的参数,这样 cppcheck 就能够递归的查看该目录下所有的文件。

然而仅仅是动态代码检测是不足够的,细心地读者应该发现了其余的内存应用问题。事实上的确有很多问题不运行是难以发现的,因而须要 valgrind 进行更加齐备的内存拜访检测。

动静查看工具 —valgrind

Valgrind 是一个用于构建动态分析工具的仪器框架。Valgrind 工具能够自动检测许多内存治理和线程谬误,并详细分析您的程序。您还能够应用 Valgrind 构建新工具。

Valgrind 发行版目前包含七个生产品质工具:一个内存谬误检测器、两个线程谬误检测器、一个缓存和分支预测分析器、一个调用图生成缓存和分支预测分析器,以及两个不同的堆分析器。可见 valgrind 是一个十分弱小的 CPP 剖析工具,在本文中次要介绍如何用 valgrind 进行内存问题的排查。

valgrind 装置

在 Ubuntu 零碎中只需运行 sudo apt install valgrind 即可开启内存检测之旅。同样能够应用 valgrind --version 来查看是否装置胜利并查看版本。作者装置的版本是 3.13.0。

valgrind 应用和报告阐明

在应用 valgrind 进行内存透露检测时肯定要用 debug 模式编译我的项目,否则 valgrind 无奈获取问题呈现的文件行数。编译完之后运行 valgrind ./${executablefile} 就能够对生成的可执行文件进行检测了。valgrind 将会执行该文件,并记录下内存应用问题的地位。在咱们检测示例失去的可执行文件,能够失去以下报告(为了节俭篇幅,只截取报告问题的局部)。

==12288== Invalid write of size 4
==12288==    at 0x1091B1: main (main.cpp:9)
==12288==  Address 0x5b7fc84 is 0 bytes after a block of size 4 alloc'd
==12288==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12288==    by 0x109176: main (main.cpp:7)


==12288== Invalid read of size 8
==12288==    at 0x109C0A: __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >::__normal_iterator(int* const&) (stl_iterator.h:783)
==12288==    by 0x109819: std::vector<int, std::allocator<int> >::begin() (stl_vector.h:564)
==12288==    by 0x10C522: Printer<int>::print() (printer.cpp:7)
==12288==    by 0x109203: main (main.cpp:17)
==12288==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

==12288== Process terminating with default action of signal 11 (SIGSEGV)
==12288==  Access not within mapped region at address 0x0
==12288==    at 0x109C0A: __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >::__normal_iterator(int* const&) (stl_iterator.h:783)
==12288==    by 0x109819: std::vector<int, std::allocator<int> >::begin() (stl_vector.h:564)
==12288==    by 0x10C522: Printer<int>::print() (printer.cpp:7)
==12288==    by 0x109203: main (main.cpp:17)

报告中次要发现了两个问题,,一个是位于 main.cpp:9 的内存拜访越界,另一个是位于 main.cpp:17 的对空指针的成员函数调用。其中第一个谬误是因为在分配内存时,谬误的应用 sizeof(),只调配了 4 字节的容量,却将其误用为大小为 100 个 int 大小的内存区域。第二个谬误是因为应用了空指针的成员变量(member,printer.cpp:7)。修改这两个谬误,持续检测该程序。

==13652== HEAP SUMMARY:
==13652==     in use at exit: 400 bytes in 1 blocks
==13652==   total heap usage: 16 allocs, 15 frees, 74,500 bytes allocated

仍然检测到异样信息,然而并没有定位到无效地位,只定位到了程序的最初一行。这时须要减少选项--leak-check=full --show-leak-kinds=all,展现更多信息。最终失去更多的信息:

==12568== 400 bytes in 1 blocks are still reachable in loss record 1 of 1
==12568==    at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12568==    by 0x109176: main (main.cpp:7)

原来是程序开始时 (main.cpp:7) 调配的内存,没有被回收,与 cppcheck 检测后果统一。

valgrind 的局限性

修改谬误之后仍然会发现上面的谬误,这是因为拜访数组 b 时呈现了越界。然而 valgrind 没有定位到具体位置,只是报了一个异样退出的谬误。这里就不得不提到 valgrind 本身的局限性:不能检测在栈上分配内存的应用正确性。因而对于这种状况,能够联合 cppcheck 来独特查看,确保内存应用的正确性。

*** stack smashing detected ***: <unknown> terminated
==13774== 
==13774== Process terminating with default action of signal 6 (SIGABRT)
==13774==    at 0x541DFB7: raise (raise.c:51)
==13774==    by 0x541F920: abort (abort.c:79)
==13774==    by 0x5468966: __libc_message (libc_fatal.c:181)
==13774==    by 0x5513B60: __fortify_fail_abort (fortify_fail.c:33)
==13774==    by 0x5513B21: __stack_chk_fail (stack_chk_fail.c:29)
==13774==    by 0x10943F: main (main.cpp:28)
退出移动版