用valgrind 检测内存错误
----------------------------------------
前言:
----------------------------------------
介绍了valgrind 是什么,工作原理。
内存错误是什么,
给出了一个综合内存错误实例。看一看valgrind 如何汇报错误.
 
----------------------------------------
1. valgrind 是什么
----------------------------------------
Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。
Valgrind由内核(core)以及基于内核的其他调试工具组成。
内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;
而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。
Valgrind的体系结构如下图所示:  上不了图,凑合着吧
 
----------------------------------------
2. valgrind 包含的工具
----------------------------------------
Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,
          比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。这也是本文将重点介绍的部分。
Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
Cachegrind。它主要用来检查程序中缓存使用出现的问题。
Helgrind。它主要用来检查多线程程序中出现的竞争问题。
Massif。它主要用来检查程序中堆栈使用中出现的问题。
Extension。可以利用core提供的功能,自己编写特定的内存调试工具
 
 
----------------------------------------
3. valgrind 工作原理
----------------------------------------
Memcheck 能够检测出内存问题,关键在于其建立了两个全局表。并且虚拟了一个cpu 环境
 
Valid-Value 表:
对于进程的整个地址空间中的每一个字节(byte),都有与之对应的 8 个 bits;
对于 CPU 的每个寄存器,也有一个与之对应的 bit 向量。这些 bits 负责记录该字节或者寄存器值是否具有有效的、已初始化的值。
 
Valid-Address 表
对于进程整个地址空间中的每一个字节(byte),还有与之对应的 1 个 bit,负责记录该地址是否能够被读写。
 
 
----------------------------------------
4. valgrind 的使用.
----------------------------------------
valgrind --help 有介绍. man valgrind 亦可。 众多的选项,
我们可能只关心几种。
检测过程:
当要读写内存中某个字节时,首先检查这个字节对应的 A bit。如果该A bit显示该位置是无效位置,memcheck 则报告非法地址读写错误。
内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit 也被加载到虚拟的 CPU 环境中。
一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。
 
常见内存错误:
****************************************
甲. 使用未初始化内存问题
----------------------------------------
全局变量和静态变量初始值为0,
而局部变量和动态申请的变量,其初始值为随机值。
如果程序使用了为随机值的变量,那么程序的行为就变得不可预期。
----------------------------------------
乙. 内存读写越界
----------------------------------------
访问了你不应该/没有权限访问的内存地址空间,  
读一下可能问题还不大(也是非法访问内存),
如果是写操作,那么后果将不可预期。使用这个内存的人就惨了。
----------------------------------------
丙. 内存覆盖
----------------------------------------
C 语言的强大和可怕之处在于其可以直接操作内存,
C 标准库中提供了大量这样的函数,比如 strcpy, strncpy, memcpy, strcat 等,
这些函数有一个共同的特点就是需要设置源地址 (src),和目标地址(dst),
src 和 dst 指向的地址不能发生重叠,否则结果将不可预期
----------------------------------------
丁. 动态内存使用的常见错误
----------------------------------------
常见的内存分配方式分三种:静态存储,栈上分配,堆上分配。
全局变量属于静态存储,它们是在编译时就被分配了存储空间,
函数内的局部变量属于栈上分配,而最灵活的内存使用方式当属堆上分配,
也叫做动态内存分配了, 见下述。
 
    动态内存使用的常见错误
****************************************
    子: 申请和释放不一致
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
由于 C++ 兼容 C,而 C 与 C++ 的内存申请和释放函数是不同的,
因此在 C++ 程序中,就有两套动态内存管理函数。
一条不变的规则就是采用 C 方式申请的内存就用 C 方式释放;用 C++ 方式申请的内存,用 C++ 方式释放。
也就是用 malloc/alloc/realloc 方式申请的内存,用 free 释放;
用 new 方式申请的内存用 delete 释放。 这主要是牵扯构造和析构过程,构造和析构往往有嵌套的内存分配操作。
用 malloc 方式申请了内存却用 delete 来释放,虽然这在很多情况下不会有问题,但这绝对是潜在的问题。
用 new 申请的内存用free 释放,由于缺少必要的析够过程,一般会引发内存泄漏。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    丑: 申请和释放不匹配
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
申请了多少内存,在使用完成后就要释放多少。如果没有释放,或者少释放了就是内存泄露;
多释放了也会产生问题。,指针p和pt指向的是同一块内存,却被先后释放了两次。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    寅: 内存泄漏
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
虽属于申请,释放不匹配,这里再强调一次.
动态申请的内存,在使用完后既没有释放,又无法被程序的其他部分访问.即内存指针已经丢失,即为内存泄漏。
通常在一个函数内分配内存,使用完后再释放,不会发生内存泄漏。
但有时根据逻辑的需要,只能在一个函数内申请,在另一个函数内释放,这就容易引发内存泄漏。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    卯: 释放后仍然读写
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
本质上说,系统会在堆上维护一个动态内存链表,如果被释放,就意味着该块内存可以继续被分配给其他部分,
如果内存被释放后再访问,就可能覆盖其他部分的信息,这是一种严重的错误, 使得程序结果不可预期!
 
---------------------------------------------------------------------
小结:
这么多的内存问题,可以概括为: 内存未初始化,非法访问内存,内存泄漏三种。
或者说非法数值,非法地址,内存泄漏三种。
非法地址也称野指针是segment fault 的罪魁祸首。野指针是未初始化指针或非法修改的指针。
数值的变动有合法修改和非法修改或考虑不周失控修改。
用gdb 可以监视每次修改的过程。
但gdb 定位bug,  远没有valgrind 使用的这么简单和直观,
valgrind 是汇报者,而gdb 是需要你自己寻找bug.
所以要各取所长。
---------------------------------------------------------------------
这么多的内存问题,难道valgrind 都能检测到吗? 嗯,基本上是这样。
下面给出一个充满内存错误的综合例子。看看结果。
这是一个温和的内存错误,并没有激发segment fault,引起操作系统大怒而杀掉进程。
 
[hjj@hjj ~/test]$ cat test.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void uninit(void)
{
    int a[5];
    a[0]=a[1]=a[2]=10;
    a[4]=a[0]+a[1]+a[2]+a[3];    // a[3] 未初始化
    printf("a[4] is %d\n", a[4]);
    a[3]=0;
    a[2]=a[6];                // 内存访问越界, memcheck 不检测栈内存越界
    printf("a[2] is %d\n", a[2]);
}
 
void overcopy(void)
{
    char src[]="123456789";
    char dst[20];
    strcat(src,"abc");        // 内存越界,memcheck 不检测栈内存越界
    strcpy(src,dst);        // 内存覆盖
}
 
void malloc_err(void)
{
    char *p1,*p2,*p3;
    p1 = (char *)malloc(10);
    p2 = p1;
    p3 = (char *)malloc(20);
    memset(p1,0,10);
    p1[10] = 5;                // 越界访问
    delete p2;                // 释放,分配不匹配
    *p1 = 10;                // 访问已释放的内存
    free(p1);                // 释放两次内存。
                            // p3 泄漏, 以后没有机会访问了
}
 
 
int main(void)
{
    uninit();
    overcopy();
    malloc_err();
    return 0;
}
----------------------------------------
编译:
[hjj@hjj ~/test]$ g++ -g -o test test.cpp
----------------------------------------
运行:
[hjj@hjj ~/test]$ valgrind  ./test  
----------------------------------------
==3007== Memcheck, a memory error detector
==3007== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==3007== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==3007== Command: ./test
==3007==  
==3007== Use of uninitialised value of size 8
==3007==    at 0x3499643D9B: _itoa_word (in /lib64/libc-2.12.so)
==3007==    by 0x3499646952: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x4007F8: uninit() (test.cpp:10)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x3499643DA5: _itoa_word (in /lib64/libc-2.12.so)
==3007==    by 0x3499646952: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x4007F8: uninit() (test.cpp:10)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x34996453E3: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x4007F8: uninit() (test.cpp:10)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x3499645401: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x4007F8: uninit() (test.cpp:10)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
a[4] is 30
==3007== Use of uninitialised value of size 8
==3007==    at 0x3499643D9B: _itoa_word (in /lib64/libc-2.12.so)
==3007==    by 0x3499646952: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x400819: uninit() (test.cpp:13)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x3499643DA5: _itoa_word (in /lib64/libc-2.12.so)
==3007==    by 0x3499646952: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x400819: uninit() (test.cpp:13)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x34996453E3: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x400819: uninit() (test.cpp:13)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x3499645401: vfprintf (in /lib64/libc-2.12.so)
==3007==    by 0x349964F489: printf (in /lib64/libc-2.12.so)
==3007==    by 0x400819: uninit() (test.cpp:13)
==3007==    by 0x4008EF: main (test.cpp:41)
==3007==  
a[2] is 4196048
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x4A0809F: strcpy (mc_replace_strmem.c:443)
==3007==    by 0x400871: overcopy() (test.cpp:21)
==3007==    by 0x4008F4: main (test.cpp:42)
==3007==  
==3007== Conditional jump or move depends on uninitialised value(s)
==3007==    at 0x4A080B7: strcpy (mc_replace_strmem.c:443)
==3007==    by 0x400871: overcopy() (test.cpp:21)
==3007==    by 0x4008F4: main (test.cpp:42)
==3007==  
==3007== Invalid write of size 1
==3007==    at 0x4008C3: malloc_err() (test.cpp:31)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  Address 0x4c2e04a is 0 bytes after a block of size 10 alloc'd
==3007==    at 0x4A06AAA: malloc (vg_replace_malloc.c:291)
==3007==    by 0x40088A: malloc_err() (test.cpp:27)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  
==3007== Mismatched free() / delete / delete []
==3007==    at 0x4A0606A: operator delete(void*) (vg_replace_malloc.c:502)
==3007==    by 0x4008D1: malloc_err() (test.cpp:32)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  Address 0x4c2e040 is 0 bytes inside a block of size 10 alloc'd
==3007==    at 0x4A06AAA: malloc (vg_replace_malloc.c:291)
==3007==    by 0x40088A: malloc_err() (test.cpp:27)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  
==3007== Invalid write of size 1
==3007==    at 0x4008D6: malloc_err() (test.cpp:33)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  Address 0x4c2e040 is 0 bytes inside a block of size 10 free'd
==3007==    at 0x4A0606A: operator delete(void*) (vg_replace_malloc.c:502)
==3007==    by 0x4008D1: malloc_err() (test.cpp:32)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  
==3007== Invalid free() / delete / delete[] / realloc()
==3007==    at 0x4A06484: free (vg_replace_malloc.c:468)
==3007==    by 0x4008E4: malloc_err() (test.cpp:34)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  Address 0x4c2e040 is 0 bytes inside a block of size 10 free'd
==3007==    at 0x4A0606A: operator delete(void*) (vg_replace_malloc.c:502)
==3007==    by 0x4008D1: malloc_err() (test.cpp:32)
==3007==    by 0x4008F9: main (test.cpp:43)
==3007==  
==3007==  
==3007== HEAP SUMMARY:
==3007==     in use at exit: 20 bytes in 1 blocks
==3007==   total heap usage: 2 allocs, 2 frees, 30 bytes allocated
==3007==  
==3007== LEAK SUMMARY:
==3007==    definitely lost: 20 bytes in 1 blocks
==3007==    indirectly lost: 0 bytes in 0 blocks
==3007==      possibly lost: 0 bytes in 0 blocks
==3007==    still reachable: 0 bytes in 0 blocks
==3007==         suppressed: 0 bytes in 0 blocks
==3007== Rerun with --leak-check=full to see details of leaked memory
==3007==  
==3007== For counts of detected and suppressed errors, rerun with: -v
==3007== Use --track-origins=yes to see where uninitialised values come from
==3007== ERROR SUMMARY: 32 errors from 14 contexts (suppressed: 4 from 4)
 
----------------------------------------
结果分析:
以上可见, 除了栈内存越界它未报出外, 其它错误全报告了。 其中有的错误例如uninit,曾反复申报。
估计是每执行一次,就报告一次。
 
其实,memcheck 作者只所以不报告栈越界错误, 是因为调试信息中不包含栈变量数据大小信息。
栈变量拿来就使用,没有分配的概念。 但对于内存覆盖,则是善意提醒。 它估计这一定不是你的初衷。
当然如果你很变态,一定要写出有内存覆盖而运转正常的程序,也不是不可以,因为一切还是可控的。
对于堆错误,则报告完善。
----------------------------------------
 
----------------------------------------
使用 --leak-check=full 选项, 进一步报告内存泄漏位置
valgrind  --leak-check=full ./test  
----------------------------------------
输出基本一样, 在HEAP SUMMARY 中, 进一步指明了泄漏的位置。
==3030== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3030==    at 0x4A06AAA: malloc (vg_replace_malloc.c:291)
==3030==    by 0x4008A0: malloc_err() (test.cpp:29)
==3030==    by 0x4008F9: main (test.cpp:43)
 
valgrind 另外常用的选项,浅显易懂。
--leak-check=full  
--show-leak-kinds=all  
--track-origins=yes   
-v

用valgrind 检测内存错误相关推荐

  1. 动态检测内存错误利器ASan

    ASan,即Address Sanitizer,是一个适用于c/c++程序的动态内存错误检测器,它由一个编译器检测模块(LLVM pass)和一个替换malloc函数的运行时库组成,在性能及检测内存错 ...

  2. c/c++动态检测内存错误利器 - ASan

    ASan,即Address Sanitizer,是一个适用于c/c++的动态内存错误检测器,它由一个编译器检测模块(LLVM pass)和一个替换malloc函数的运行时库组成,在性能及检测内存错误方 ...

  3. 谈谈如何利用 valgrind 排查内存错误

    谈谈如何利用 valgrind 排查内存错误 - 云+社区 - 腾讯云 (tencent.com) 高性能服务器开发关于Socket编程的内容 - 云+社区 - 腾讯云 (tencent.com)

  4. ASAN 检测内存错误 debug

    ASAN 检测内存错误 debug 编译时添加选项 -fsanitize=address -fno-omit-frame-pointer 或在封装器中处理-c时添加编译选项,在链接阶段添加-lasan ...

  5. linux环境下QT程序内存泄露检测-Qt creator +Valgrind检测内存泄漏(linux)

    原文查看具体内容: Qt creator +Valgrind检测内存泄漏(linux)_枫影竹韵的博客-CSDN博客 一.说明 由于Qt creator和valgrind是相互独立的,所以需要安装va ...

  6. 使用valgrind检测内存问题

    valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. 1 valgrind安装 可以到官网下载最新的源码包:valgrind官网下载,也可以直接使用 c_utils/debug ...

  7. securecrt遇到一个致命的错误且必须关闭_谈谈如何利用 valgrind 排查内存错误

    导读 Valgrind 最为开发者熟知和广泛使用的工具莫过于 Memcheck,它是检查 c/c++ 程序内存错误的神器,报告结果非常之精准. 本文主要分享作者在使用该神器解决内存问题的过程中积累的一 ...

  8. valgrind检测内存泄漏,gperftools,memwatch和性能优化Messy_Test

    yum -y install  valgrind 如何在windows使用valgrind_如何在windows下使用f2py_weixin_39621870的博客-CSDN博客 QNX下Valgri ...

  9. ubuntu下Qt Creator使用valgrind检测内存泄漏

    1.安装valgrind sudo apt-get install valgrind 2. Valgrind的使用 为了使valgrind发现的错误更精确,如能够定位到源代码行,建议在编译时加上-g参 ...

最新文章

  1. HTTP协议详解(真的很经典)
  2. Ant编译编译APK、打包打包JS
  3. 学习手机游戏原画设计需要多长时间?难学吗?
  4. 科技部成立新一代人工智能发展研究中心
  5. 事理逻辑为核心的自然语言处理理论实践与工业探索项目
  6. 2015毕业找工作纪实|一年的蜕变毕业生
  7. 小米真香产品被友商怼:只有USB2.0接口 怎么播高码率的视频?
  8. java 集合 总结 表_java 列表与集合总结
  9. 基于单片机的智能交通灯控制系统的设计
  10. ssh介绍和使用--SecureCRT工具和ssh命令使用,以及sshd配置
  11. 强化学习之AC、A2C和A3C
  12. win10开机出现任务栏卡死无反应,桌面点击正常,重启任务管理器无效
  13. 3.字体样式,分隔线与段落
  14. 国内镜像加速器registry的制作
  15. 抖音引流必须知道的三个要点!!!!
  16. 北航新版选课系统科学选课教程
  17. 指标体系:指标的设计方法
  18. 不存在放之四海皆准的解决方案
  19. coreos 安装 mysql_技术|手把手教你在 CoreOS 上构建你的第一个应用
  20. Motif-based Graph Self-Supervised Learning for Molecular Property Prediction

热门文章

  1. 期货持仓不足(期货持仓量越来越少)
  2. C++多态的用法详解
  3. 微信小程序的拍照功能
  4. jquery mobile ajax 实例,jQuery Mobile 表单
  5. 朗驰欣创——嵌入式软件面试
  6. ip地址、域名、DNS、URL的区别与联系
  7. js的offsetleft属性的用法
  8. C语言——结构体运用 编程统计候选人的得票数。设有3个候选人zhang、li、wang(候选人姓名不区分大小写),10个选民,选民每次输入一个得票的候选人的名字,若选民输错候选人姓名,则按废票处理。
  9. openwrt编译流程分析
  10. Python练习案例(二)——斐波那契数列