写 C++的同窗想必有太多和内存打交道的血泪经验了,经常被 C++的内存问题搅的焦头烂额。(写 core 的经验了)有不少同窗一见到 core 就两眼一抹黑,不知所措了。笔者 入"坑"C++以后,在调试 C++代码的过程之中,学习了很多调试代码内存的工具。但愿借这个机会来介绍一下笔者经常使用的工具,GDB,Valgrind等等,相信你们经过好好运用这些工具,能更好的驯服内存这匹"野马"。html
CoreDump时一个二进制的文件,进程发生错误崩溃时,内核会产生一个瞬时的快照,记录该进程的内存、运行堆栈状态等信息保存在core文件之中。作个简单的类比,core 文件至关于飞机运行时的"黑匣子",可以帮助咱们更好的调试 C++程序的问题。OK,接下来笔者将介绍一下若是利用GDB 来调试 CoreDump的文件。java
首先咱们先肯定一下操做系统是否会产生 CoreDump 文件。经过ulimit -c
获取 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文件保存位置和文件格式。(建议将后缀改成进程号) 笔者这里简单起见,不进行修改了。性能
#include <unistd.h> #include <thread> void core() { char* ch = nullptr; *ch = 'a'; } int main() { auto t1 = std::thread(core); sleep(5); return 0; }
编译运行该代码,产生段错误,生成了 core 文件
学习
利用 GDB 调试 core 文件
调试 core 文件须要利用原生编译出的二进制文件调试。这里有一点须要注意的,若是编译 C++文件之时没有加-g的编译选项,core 文件的调试内容会不够完整。笔者这里建议开启对应的编译选项,这会致使对应的二进制文件变大,编译时间变长。(生产环境能够考虑关闭)使用gdb 二进制文件 core 文件
打开 core 文件。测试
core 文件列出了两个线程的信息。咱们须要判断对应的问题代码的定位,接下来咱们一块儿来梳理一下:
用info thread
查看线程的运行状况,在这里咱们就能够判断代码 core 在什么线程之中了,若是仍是没法肯定,能够经过thread apply all bt
列出更加详尽的堆栈信息。优化
经过上述信息能够确认,thread 1的代码存在问题。咱们经过thread 1
切换到 thread 1,用bt
显示堆栈信息继续追查:
ui
以后咱们来看看使人生疑的栈内容,这里显然栈0是咱们怀疑的代码,用frame 1
查看。
好了,这里咱们找到了引发问题罪魁祸首的代码,访问了空指针。
程序运行的 core 文件是咱们调试代码十分重要依据,经过 GDB 能够很好的给出咱们修改代码的线索和参考,熟悉掌握GDB 的调试技巧,可以大大解放咱们调试问题代码的生产力。
亡羊补牢不如未雨绸缪,与其等到出现程序崩溃时使用 GDB 来调试解决,不如事前确认代码之中可能引起的问题。因此笔者接下来要介绍一款来自大不列颠的C++代码分析神器:Valgrind。(Valgrind的做者也经过开发Valgrind得到了第二届Google-O'Reilly开源代码大奖~~~)
Valgrind 十分强大,适用于内存分析,泄漏检测、锁分析,性能评估。笔者也只掌握了一些基本的入门使用。但愿这里可以抛砖引玉,更多复杂的用法烦请参考官方文档。
Valgrind的安装很简单,笔者的发行版带了对应的 deb 包。经过 apt-get 的包管理工具就能够直接安装了,其余的发行版也能够做为参考。
sudo apt-get install valgrind
与 GDB 相似,Valgrind 一样推荐使用-g
做为编译参数。可以更好的对代码进行分析。这里咱们依旧使用以前的例子进行测试:
valgrind ./untitiled
下面是 Valgrind 的分析结果:
这里有显示Invalid write of size 1
,说明这里有一个不合法的写入,而且写入了1个字节的内容。也就是指的是咱们以前代码之中写入空指针的行为。
接下来咱们要展现 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
接下来咱们来分析对应的结果:
上图显示了各个函数的被调用的耗时百分比,咱们能够选取对性能感兴趣的函数来进行深刻分析。咱们下面继续分析其中一个函数被调用和它使用函数的性能状况
因此经过上述数据,咱们能够给出性能分析的证据和线索,依据这些信息来更好的优化咱们代码的性能。
本文介绍了亡羊补牢的工具 GDB,也简介了未雨绸缪的Valgrind 。经过上述工具对C++程序更加深刻分析。工欲善其事,必先利其器,但愿你们也能好好掌握这些提供生产力的工具,让 C++再也不恼人。