Memcheck是Valgrind工具集中最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc、free、new、delete的调用都会被捕获。Memcheck之所以能够检测出这些内存问题,关键在于其建立了两个全局表:
(1)Valid-Value表
对于进程的整个地址空间中的每一个字节(byte)都有与之对应的8个bits,对于CPU的每个寄存器,也有一个与之对应的bit向量,这些bits负责记录该字节或者寄存器值是否具有有效的、已初始化的值。
(2)Valid-Address表
对于进程整个地址空间中的每一个字节(byte),还有与之对应的1个bit负责记录该地址是否可写。
检测原理如下:
(1)当要读写内存中某个字节时,首先检查这个字节对应的A bit,如果该A bit显示该位置是无效位置,Memcheck则报告读写错误。
(2)内核(core)类似于一个虚拟的CPU环境,这样当内存中的某个字节被加载到真实的CPU中时,该字节对应的V bit也被加载到虚拟的CPU环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则Memcheck会检查对应的V bits,如果该值尚未被初始化,则会报告使用未初始化内存错误。
需要注意的是,在编译程序的时候最好加入-g参数,便于Valgrind读入符号表之类的信息以提供更丰富的错误定位信息,不推荐加入-O等优化参数,因为优化后的代码易于让Valgrind解释错误。下面将针对Memcheck能够检测的七种问题进行详细介绍。
一、未初始化内存
对于
位于程序中不同段的变量,其初始值是不同的,全局变量和静态变量初始值为0,而局部变量和动态申请的变量,其初始值为随机值。如果程序使用了为随机值的变量,那么程序的行为就变得不可预期。下面程序将演示一种常见的使用了未初始化变量的场景,局部变量s初始化值为随机值,在条件语句中使用变量s来作if条件变量:
编译程序,分析结果:
输出结果显示,在该程序第6行中,if条件语句的跳转依赖于一个未初始化的变量s,准确地发现了该程序中存在的问题。
二、内存读写越界
这种情况是指,访问了你不应该或者没有权限访问的内存地址空间,比如:访问数组越界、对动态内存访问时超出了申请的内存大小范围。下面程序将演示一种典型的数组越界问题,pt是一个局部数组变量,其大小为4,在用for循环初始化数组元素时,对数组进行了越界写操作,这将导致不可预期的后果:
编译程序,分析结果:
输出结果显示,在该程序的第8行,进行非法的写操作,在该程序的第9行,进行了非法读操作,准确地发现了内存读写越界问题。
三、内存覆盖
C标准库中提供了大量直接操作内存的函数,比如:
memcpy、strcpy、strncpy、strcat等,这些函数有一个共同特点就是需要设置源地址src和目标地址dst,src和dst指向的地址不能发生重叠,否则结果将不可预期。下面程序将演示一种典型的内存重叠或覆盖的问题,src和dst所指向的地址相差20,但指定的拷贝长度却是21,这样就会把之前拷贝的值覆盖:
编译程序,分析结果:
输出结果显示,在该程序的第12行,源地址和目标地址数据拷贝出现重叠,准确地发现了内存覆盖问题。
四、申请和释放方式不匹配
为了兼容C,在C++中有两套动态内存管理机制,分别是C方式的malloc/alloc/realloc/free,C++方式的new/delete,这两种方式不能混用,否则会有潜在问题存在。下面程序将演示一种典型的申请和释放不匹配的问题,用malloc申请的内存由delete释放,用new申请的内存由free释放:
编译程序,分析结果:
输出结果显示,在该程序的第6行由malloc分配内存,在第7行由delete释放,第8行由new分配内存,在第9行由free释放,准确发现了内存申请和释放不匹配的问题。
五、释放后仍然读写
系统在堆上维护一个动态内存链表,如果动态申请的内存被释放,就意味着这块内存可以继续被分配给其它部分,如果内存被释放后再访问,就可能覆盖其它部分信息,这是一种严重的错误。
下面程序将演示一种典型的释放后仍然使用的问题,用new申请的内存由delete释放后仍然修改其值:
编译程序,分析结果:
输出结果显示,在该程序的第7行delete了第6行由new分配的内存后第8行仍然写这块内存,准确发现了内存释放后仍然读写该块内存的问题。
六、内存泄露
内存泄露,狭义上来说是指向一块内存的指针永远丢失,但是在此引申一下。申请了多少内存,在使用完成后就要释放多少,如果没有释放或者释放少了都属于内存泄露。当然多释放了也会产生问题。下面程序将演示这几种情况:
编译程序,分析结果:
输出结果显示,在该程序的第7行new出了10个元素的数组,第8行delete时需要用delete [],第9行new出的内存,第10行和第11行分别delete,这样就多少释放了一次,第6行new出的内存没有释放.
在LEAK SUMMARY中:
(1)definitely lost:没有任何指针指向该区域,已经造成了内存泄露。
(2)indirectly lost:指向该内存的指针也位于内存泄露处。
(3)possibly lost:仍然存在某个指针能够访问某块内存,但该指针指向的已经不是该内存首地址。
(4)still reachable:仍有指针引用该内存块,只是没有释放而已,可以通过设置--show-reachable=yes来报错。
阅读(5178) | 评论(1) | 转发(1) |