目录
Valgrind
一.摘要
Valgrind是运行在linux上的程序分析工具,它包含很多小工具: memcheck(内存泄露检查工具)等。
二.安装Valgrind
注意: 这里通过源码安装 Valgrind 3.18.1版本, 因为apt方式安装的版本过旧,会有一些使用上问题
(1) 打开网址:https://valgrind.org/downloads/current.html#current, 这里以下载Valgrind 3.18.1(tar.bz2)压缩包为例
(2)编译安装
./configure --prefix=[你自己的安装目录] 设置环境变量时需要用到该地址
make
make install
vim ~/.bashrc 在最后一行添加 PATH=${PATH}:/valgrind/bin [你自己的安装目录]
source ~/.bashrc
(3) 测试安装版本
valgrind --version
显示如下信息则说明安装成功
valgrind-3.18.1
(4) Qt creator中设置新安装的Valgrind路径和参数
三,简单上手和分析
参考:Linux 性能分析valgrind(一)之memcheck使用
命令(以下程序均可以使用此命令):
valgrind --log-file=valgrind.log --tool=memcheck --leak-check=full --show-leak-kinds=all ./your_app arg1 arg2
–log-file :报告文件名。如果没有指定,输出到stderr
–tool=memcheck:指定Valgrind使用的工具。Valgrind是一个工具集,包括Memcheck、Cachegrind、Callgrind等多个工具。memcheck是缺省项。
–leak-check: 指定如何报告内存泄漏(memcheck能检查多种内存使用错误,内存泄漏是其中常见的一种),可选值有:
- no 不报告
- summary 显示简要信息,有多少个内存泄漏。summary是缺省值。
- yes 和 full 显示每个泄漏的内存在哪里分配。
–show-leak-kinds: 指定显示内存泄漏的类型的组合。类型包括definite, indirect, possible,reachable。也可以指定all或none。缺省值是definite,possible
。 运行一段时间后想停止进程不要kill掉,需要ctrl + c
来结束,输出的log会在上述命令中的valgrind.log
中。
程序1(C程序):使用未初始化的内存
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p;
char c = *p;
printf("\n [%c]\n",c);
return 0;
}
Valgrind重点结果信息: 使用未初始化的变量 , 无效的读( 读取没有分配地址空间的区域数据 )
==73374== Use of uninitialised value of size 8
==73374== at 0x400513: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
==73374==
==73374== Invalid read of size 1
==73374== at 0x400513: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
==73374== Address 0x0 is not stack'd, malloc'd or (recently) free'd
程序2(C程序):在内存被释放后进行读/写
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
free(p);
c = *p;
return 0;
}
Valgrind重点结果信息:
==74181== Invalid read of size 1
==74181== at 0x4005E3: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
==74181== Address 0x520a040 is 0 bytes inside a block of size 1 free'd
==74181== at 0x4C3195F: free (vg_replace_malloc.c:872)
==74181== by 0x4005DE: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
==74181== Block was alloc'd at
==74181== at 0x4C2F075: malloc (vg_replace_malloc.c:381)
==74181== by 0x4005A8: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
程序3(C程序): 内存泄露
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p = malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
return 0;
}
Valgrind重点结果信息:直接泄露
==74814== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==74814== at 0x4C2F075: malloc (vg_replace_malloc.c:381)
==74814== by 0x400558: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
程序4(C++程序): 不匹配使用malloc free 和 new delete
#include <stdio.h>
#include <stdlib.h>
#include<iostream>
int main(void)
{
char *p = (char*)malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
delete p;
return 0;
}
Valgrind重点结果信息:
==75341== by 0x400683: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
==75341== Address 0x5b20c80 is 0 bytes inside a block of size 1 alloc'd
==75341== at 0x4C2F075: malloc (vg_replace_malloc.c:381)
==75341== by 0x400648: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
程序5(C程序): 两次释放内存
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *p = (char*)malloc(1);
*p = 'a';
char c = *p;
printf("\n [%c]\n",c);
free(p);
free(p);
return 0;
}
Valgrind重点结果信息:
==76126== Invalid free() / delete / delete[] / realloc()
==76126== at 0x4C3195F: free (vg_replace_malloc.c:872)
==76126== by 0x4005EA: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
==76126== Address 0x520a040 is 0 bytes inside a block of size 1 free'd
==76126== at 0x4C3195F: free (vg_replace_malloc.c:872)
==76126== by 0x4005DE: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
==76126== Block was alloc'd at
==76126== at 0x4C2F075: malloc (vg_replace_malloc.c:381)
==76126== by 0x4005A8: main (in /home/bossdog/3Growup/valgrind/test1/a.exe)
四.Qt中使用Valgrind
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(test LANGUAGES C)
add_executable(test main.c)
main.c
#include <stdlib.h>
void f(void)
{
int* x = malloc(10 * sizeof(int));
x[10] = 0; // problem 1: heap block overrun
} // problem 2: memory leak -- x not freed
int main(void)
{
for(int i=0;i<100;i++)
{
f();
}
f();
f();
f();
return 0;
}
点击 Tool -> Valgrind Memory Analyzer 选项
查看分析结果:
可以看到,同一个位置去调用一个内存泄露函数,被统计为1次record.,点击后面的蓝色字体可以看到具体内存泄漏的代码位置。
五.内存泄露分析
valgrind 将内存泄漏分为 4 类。 参考
- 明确泄漏(definitely lost):内存还没释放,但已经没有指针指向内存,内存已经不可访问
- 间接泄漏(indirectly lost):泄漏的内存指针保存在明确泄漏的内存中,随着明确泄漏的内存不可访问,导致间接泄漏的内存也不可访问
- 可能泄漏(possibly lost):指针并不指向内存头地址,而是指向内存内部的位置
- 仍可访达(still reachable):指针一直存在且指向内存头部,直至程序退出时内存还没释放。
PS:这里只展开 definitely lost 和 indirectly lost
明确泄漏: 内存没释放,但已经没有任何指针指向这片内存,内存地址已经丢失
间接泄漏: 间接泄漏就是指针并不直接丢失,但保存指针的内存地址丢失了
struct list {
struct list *next;
};
int main(int argc, char **argv)
{
struct list *root;
root = (struct list *)malloc(sizeof(struct list));
root->next = (struct list *)malloc(sizeof(struct list));
printf("root %p roop->next %p\n", root, root->next);
root = NULL;
return 0;
}
丢失的是 root 指针,导致 root 存储的 next 指针成为了间接泄漏。
可能泄漏: valgrind之所以会怀疑可能泄漏,是因为指针已经偏移,并没有指向内存头,而是有内存偏移,指向内存内部的位置.
有些时候,这并不是泄漏,因为这些程序就是这么设计的,例如为了实现内存对齐,额外申请内存,返回对齐后的内存地址。但更多时候,是我们不小心 p++
了
仍可访达: 仍可访达 表示在程序退出时,不管是正常退出还是异常退出,内存申请了没释放,都属于仍可访达的泄漏类型. 如果测试的程序是正常退出的,那么这些 仍可访达 的内存就是泄漏,最好修复了。如果测试是长期运行的程序,通过信号提前终止,那么这些内存就大概率并不是泄漏。
其他内存错误
- 非法读/写内存(Illegal read / Illegal write errors)
- 使用未初始化的变量(Use of uninitialised values)
- 系统调用传递不可访问或未初始化内存(Use of uninitialised or unaddressable values in system calls)
- 非法释放(Illegal frees)
- 不对应的内存申请和释放(When a heap block is freed with an inappropriate deallocation function)
- 源地址和目的地址重叠(Overlapping source and destination blocks)
- 内存申请可疑大小(Fishy argument values)