C++雾中风景番外篇3:GDB与Valgrind ,调试代码内存的工具

写 C++的同窗想必有太多和内存打交道的血泪经验了,经常被 C++的内存问题搅的焦头烂额。(写 core 的经验了)有不少同窗一见到 core 就两眼一抹黑,不知所措了。笔者 入""C++以后,在调试 C++代码的过程之中,学习了很多调试代码内存的工具。但愿借这个机会来介绍一下笔者经常使用的工具,GDB,Valgrind等等,相信你们经过好好运用这些工具,能更好的驯服内存这匹"野马"。html

1.利用 GDB 调试 CoreDump

CoreDump时一个二进制的文件,进程发生错误崩溃时,内核会产生一个瞬时的快照,记录该进程的内存、运行堆栈状态等信息保存在core文件之中。作个简单的类比,core 文件至关于飞机运行时的"黑匣子",可以帮助咱们更好的调试 C++程序的问题。OK,接下来笔者将介绍一下若是利用GDB 来调试 CoreDump的文件。java

  • CoreDump 文件的大小

首先咱们先肯定一下操做系统是否会产生 CoreDump 文件。经过ulimit -c获取 core 文件的限制大小:
查看 core 文件的大小限制app

上面显示笔者电脑的 core 文件的大小是0,咱们须要调整一下。经过ulimit调整为无限制。固然这种调整是临时的,reboot 以后就恢复为0了。函数

ulimit -c ulimited

若是须要永久修改,能够经过/etc/security/limits.conf 来修改 core 文件的大小。工具

  • CoreDump 文件的生成路径
    默认状况下,core dump生成的文件名为core,并且就在程序当前目录下。经过修改/proc/sys/kernel/core_pattern能够控制core文件保存位置和文件格式。(建议将后缀改成进程号) 笔者这里简单起见,不进行修改了。性能

  • 编写core 代码,这里笔者利用线程访问了空指针
#include <unistd.h>
#include <thread>

void core() {
    char* ch = nullptr;
    *ch = 'a';
}

int main() {
    auto t1 = std::thread(core);
    sleep(5);
    return 0;
}
  • 编译运行该代码,产生段错误,生成了 core 文件
    图片.png学习

  • 利用 GDB 调试 core 文件
    调试 core 文件须要利用原生编译出的二进制文件调试。这里有一点须要注意的,若是编译 C++文件之时没有加-g的编译选项,core 文件的调试内容会不够完整。笔者这里建议开启对应的编译选项,这会致使对应的二进制文件变大,编译时间变长。(生产环境能够考虑关闭)使用gdb 二进制文件 core 文件打开 core 文件。测试

利用 gdb 调试 core 文件

core 文件列出了两个线程的信息。咱们须要判断对应的问题代码的定位,接下来咱们一块儿来梳理一下:
info thread查看线程的运行状况,在这里咱们就能够判断代码 core 在什么线程之中了,若是仍是没法肯定,能够经过thread apply all bt列出更加详尽的堆栈信息。优化

用 info thread 查看线程运行状况
利用 thread apply all bt 显示详尽的堆栈信息
经过上述信息能够确认,thread 1的代码存在问题。咱们经过thread 1切换到 thread 1,用bt显示堆栈信息继续追查:
Thread 1的堆栈信息ui

以后咱们来看看使人生疑的栈内容,这里显然栈0是咱们怀疑的代码,用frame 1查看。
对应存在『问题』的语句

好了,这里咱们找到了引发问题罪魁祸首的代码,访问了空指针。

小结

程序运行的 core 文件是咱们调试代码十分重要依据,经过 GDB 能够很好的给出咱们修改代码的线索和参考,熟悉掌握GDB 的调试技巧,可以大大解放咱们调试问题代码的生产力。

2.利用Valgrind判断内存泄露

亡羊补牢不如未雨绸缪,与其等到出现程序崩溃时使用 GDB 来调试解决,不如事前确认代码之中可能引起的问题。因此笔者接下来要介绍一款来自大不列颠的C++代码分析神器:Valgrind。(Valgrind的做者也经过开发Valgrind得到了第二届Google-O'Reilly开源代码大奖~~~)
Valgrind 十分强大,适用于内存分析,泄漏检测、锁分析,性能评估。笔者也只掌握了一些基本的入门使用。但愿这里可以抛砖引玉,更多复杂的用法烦请参考官方文档

Valgrind的安装

Valgrind的安装很简单,笔者的发行版带了对应的 deb 包。经过 apt-get 的包管理工具就能够直接安装了,其余的发行版也能够做为参考。

sudo apt-get install valgrind
Valgrind的使用

与 GDB 相似,Valgrind 一样推荐使用-g做为编译参数。可以更好的对代码进行分析。这里咱们依旧使用以前的例子进行测试:

valgrind ./untitiled

下面是 Valgrind 的分析结果:
valgrind 的分析结果

这里有显示Invalid write of size 1,说明这里有一个不合法的写入,而且写入了1个字节的内容。也就是指的是咱们以前代码之中写入空指针的行为。

接下来咱们要展现 Valgrind更增强大的功能。它展现了程序的内存使用状况,而且给出总结:
valgrind 对内存的分析
这里列出了多种的内存泄露状况:

  • definitely lost: 确定的内存泄漏,这表示在程序退出时,有内存没有回收,可是也没有指针指向该内存。这种状况最为严重。

  • indirectly lost: 间接的内存泄漏,如类之中定义的指针指向的内存没有回收。这种状况和上述相同。

  • possibly lost: 可能出现内存泄漏。这种状况须要仔细排查,可能代码没有问题,也可能有异常的内存泄露。

  • still reachable: 程序没主动释放内存,在退出时候该内存仍能访问到。这种状况通常问题不大,由于程序退出以后操做系统会回收程序的内存,因此这种状况通常问题不大。

这里没有给出具体泄露的内容,须要加入参数--leak-check=full将完整的结果打印出来,会指出对应的引发内存泄露的具体代码,能够继续深刻分析。

代码调优

这里进行代码调优的时,须要利用qcachegrind来进行分析。首先笔者先进行安装:

sudo apt-get install qcachegrind

以后咱们调用Valgrind来生成运行数据:

valgrind --tool=callgrind -v main(须要分析的程序)

运行以后在目录下生成对应的分析数据,咱们用qcachegrind 打开,这里用的代码是笔者以前实现的 SkipList

qcachegrind callgrind.out.29235

接下来咱们来分析对应的结果:
valgrind 的分析结果

上图显示了各个函数的被调用的耗时百分比,咱们能够选取对性能感兴趣的函数来进行深刻分析。咱们下面继续分析其中一个函数被调用和它使用函数的性能状况
insert 的函数被外调用的状况
insert 函数调用函数的状况与耗时分析

因此经过上述数据,咱们能够给出性能分析的证据和线索,依据这些信息来更好的优化咱们代码的性能。

3.小结

本文介绍了亡羊补牢的工具 GDB,也简介了未雨绸缪的Valgrind 。经过上述工具对C++程序更加深刻分析。工欲善其事,必先利其器,但愿你们也能好好掌握这些提供生产力的工具,让 C++再也不恼人

相关文章
相关标签/搜索