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)