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

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

#### 1.利用 GDB 调试 CoreDump

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

* CoreDump 文件的大小

首先我们先确定一下操作系统是否会产生 CoreDump 文件。通过```ulimit -c```获取 core 文件的限制大小:

![查看 core 文件的大小限制](https://upload-images.jianshu.io/upload_images/8552201-8af96b3c882e5b62.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

上面显示笔者电脑的 core 文件的大小是0,我们需要调整一下。通过```ulimit```调整为无限制。当然这种调整是临时的,reboot 之后就恢复为0了。

```java

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](https://upload-images.jianshu.io/upload_images/8552201-8de562e952a5fff5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

* 利用 GDB 调试 core 文件

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

![利用 gdb 调试 core 文件](https://upload-images.jianshu.io/upload_images/8552201-1d0a866142a93558.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

core 文件列出了两个线程的信息。我们需要判断对应的问题代码的定位,接下来我们一起来梳理一下:

用```info thread```查看线程的运行情况,在这里我们就可以判断代码 core 在什么线程之中了,如果还是无法确定,可以通过```thread apply all bt```列出更加详尽的堆栈信息。

![用 info thread 查看线程运行情况](https://upload-images.jianshu.io/upload_images/8552201-3a8984f96eb235b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![利用 thread apply all bt 显示详尽的堆栈信息](https://upload-images.jianshu.io/upload_images/8552201-e2e59c3fc886df42.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

通过上述信息可以确认,thread 1的代码存在问题。我们通过```thread 1```切换到 thread 1,用```bt```显示堆栈信息继续追查:

![Thread 1的堆栈信息](https://upload-images.jianshu.io/upload_images/8552201-081c527896be246d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

之后我们来看看令人生疑的栈内容,这里显然栈0是我们怀疑的代码,用```frame 1```查看。

![对应存在『问题』的语句](https://upload-images.jianshu.io/upload_images/8552201-be03dc95aa69a50d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

##### 小结

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

#### 2.利用Valgrind判断内存泄露

**亡羊补牢不如未雨绸缪**,与其等到出现程序崩溃时使用 GDB 来调试解决,不如事前确认代码之中可能引发的问题。所以笔者接下来要介绍一款来自大不列颠的C++代码分析神器:**Valgrind**。(Valgrind的作者也通过开发Valgrind获得了第二届Google-O'Reilly开源代码大奖~~~)

Valgrind 十分强大,适用于**内存分析,泄漏检测、锁分析,性能评估**。笔者也只掌握了一些基本的入门使用。希望这里能够抛砖引玉,更多复杂的用法烦请参考[官方文档](http://valgrind.org/docs/manual/QuickStart.html)。

##### Valgrind的安装

Valgrind的安装很简单,笔者的发行版带了对应的 deb 包。**通过 apt-get 的包管理工具就可以直接安装了**,其他的发行版也可以作为参考。

```

sudo apt-get install valgrind

```

##### Valgrind的使用

与 GDB 类似,Valgrind 同样推荐使用```-g```作为编译参数。能够更好的对代码进行分析。这里我们依旧使用之前的例子进行测试:

```java

valgrind ./untitiled

```

下面是 Valgrind 的分析结果:

![valgrind 的分析结果](https://upload-images.jianshu.io/upload_images/8552201-e900690b92ab7710.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

接下来我们要展示 Valgrind更加强大的功能。它展示了程序的**内存使用情况**,并且给出总结:

![valgrind 对内存的分析](https://upload-images.jianshu.io/upload_images/8552201-9a889f5635aec08b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

这里列出了多种的内存泄露情况:

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

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

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

* **still reachable**: 程序没主动释放内存,在退出时候该内存仍能访问到。这种情况一般问题不大,因为程序退出之后操作系统会回收程序的内存,所以这种情况一般问题不大。

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

##### 代码调优

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

```java

sudo apt-get install qcachegrind

```

之后我们调用Valgrind来生成运行数据:

```

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

```

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

```java

qcachegrind callgrind.out.29235

```

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

![valgrind 的分析结果](https://upload-images.jianshu.io/upload_images/8552201-89f533b56b608c35.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

上图显示了**各个函数的被调用的耗时百分比,我们可以选取对性能感兴趣的函数来进行深入分析。我们下面继续分析其中一个函数被调用和它使用函数的性能情况**

![ insert 的函数被外调用的情况](https://upload-images.jianshu.io/upload_images/8552201-f06f188afe3b9be3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![insert 函数调用函数的情况与耗时分析](https://upload-images.jianshu.io/upload_images/8552201-926183cee90bf937.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

所以通过上述数据,**我们可以给出性能分析的证据和线索,依据这些信息来更好的优化我们代码的性能。**

#### 3.小结

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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏cloudskyme

JAVA高性能I/O设计模式

同步阻塞模式。在JDK1.4以前,使用Java建立网络连接时,只能采用BIO方式,在服务器端启动一个ServerSocket,然后使用accept等待客户端请求...

18520
来自专栏Linyb极客之路

操作日志追踪记录之MDC入门

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。某些...

22420
来自专栏owent

Linux 编译安装 GCC 4.9

GCC4.9发布啦,本脚本在之前4.8的基础上做了稍许改进,更新 PS:4.9.0 开始支持C++1y特性 GCC 4.9 的大致变更如下,因为我只用C/C...

60910
来自专栏Java后端技术

解决eclipse中egit中的cannot open git-upload-pack问题

  今天在使用eclipse的egit插件进行检出远程代码到本地时,出现了cannot open git-upload-pack错误,后经过努力解决该问题,记录...

11110
来自专栏Linyb极客之路

分布式配置中心之Apollo简易环境部署

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治...

27620
来自专栏蛋未明的专栏

myweb0.2版本(更新)

16530
来自专栏Django Scrapy

四周实现爬虫系统之安装 Jupyter Notebook highchart

windows:本机安装pycharm后 系统会自动安装pip 咱们都用pip安装 1安装 Jupyter Notebook 直接输入: ''' p...

37370
来自专栏FreeBuf

Kali下常用安全工具中文参数说明(160个)

*本文原创作者:屌丝绅士,属Freebuf原创奖励计划,转载请注明来自FreeBuf 由于篇幅有限,只列举部分,ps:第一次发有什么不对的 还望各位大大指正 n...

1.3K90
来自专栏SpringBoot 核心技术

SpringCloud组件 & 源码剖析:Eureka服务注册方式流程全面分析

在SpringCloud组件:Eureka服务注册是采用主机名还是IP地址?文章中我们讲到了服务注册的几种注册方式,那么这几种注册方式的源码是怎么实现的呢?我们...

11410
来自专栏转载gongluck的CSDN博客

Brpc学习:简单回显服务器/客户端

sudo apt-get install git g++ make libssl-dev sudo apt-get install realpath libgf...

2.6K60

扫码关注云+社区

领取腾讯云代金券