C语言是嵌入式开发中永远回避不了的语言,不管是操作系统还是裸机开发,无处不体现着C语言的高效。 C语言可以直接操作内存,有着完善的内存管理机制,用好了可以削铁如泥,用不好自断一臂!
不管是C还是C++,很多同学都不可避免犯内存泄漏的错误。因为我们平时写的都是些小程序,就算申请的内存没有释放,也不会影响程序结果,所以很少会关注内存泄漏的问题。但是如果把这个问题放在商用的项目中,就会是一个不小的隐患。
如何判断程序中是否存在内存泄漏,今天就来跟大家分享一个常用工具-- valgrind。
valgrind 堪称Linux内存调试神器,可以用它检测内存泄漏、野指针,或者检查函数调用、缓存、堆栈使用问题。先看下 valgrind 的man手册:
SYNOPSIS
valgrind [valgrind-options] [your-program] [your-program-options]
TOOL SELECTION OPTIONS
The single most important option.
--tool=<toolname> [default: memcheck]
Run the Valgrind tool called toolname, e.g. memcheck, cachegrind, callgrind, helgrind, drd, massif,
lackey, none, exp-sgcheck, exp-bbv, exp-dhat, etc.
它的主要功能都放在【–tool=】选项。
memcheck:这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。
callgrind:它主要用来检查程序中函数调用过程中出现的问题。
cachegrind:它主要用来检查程序中缓存使用出现的问题。
helgrind:它主要用来检查多线程程序中出现的竞争问题。
massif:它主要用来检查程序中堆栈使用中出现的问题。
extension:可以利用core提供的功能,自己编写特定的内存调试工具。
test1.c
#include
int main()
{
int *p;
printf("%d\n", *p);
return 0;
}
编译的时候最好加上【-g】选项,【-g】选项可以在调试的时候看到行号。
通过 valgrind 调试程序。
调试信息清楚的告诉我们,程序第7行存在问题:1、使用了未初始化的指针;2、非法访问指针。导致程序最后出现段错误。
test2.c
#include
#include
#include
int main()
{
int *p = (int *)malloc(sizeof(int) * 10);
memset(p, 0, 10);
return 0;
}
运行结果:
加上【–leak-check=full】再次运行:
这次运行,明确指出了程序第7行存在内存泄漏,申请了内存,但是最后并没有释放掉。
test3.c
#include
#include
#include
int main()
{
int *p = (int *)malloc(sizeof(int) * 10);
memset(p, 0, sizeof(int));
free(p);
printf("%d\n", p[0]);
return 0;
}
运行结果:
程序中虽然使用了野指针,但是运行并没有出现段错误。这就是C语言内存管理不严格的地方, 只有访问了系统保护的内存,才会出现段错误,虽然内存被释放了,但并没有被保护,所以就算访问,也不会出现问题。 但是这种不合法的使用还是能被valgrind检测出来。
test4.c
#include
#include
int main()
{
char *p = (char *)malloc(sizeof(char) * 16);
p[20] = 'x';
free(p);
return 0;
}
运行结果:
test5.c
#include
#include
int main()
{
char *p = (char *)malloc(sizeof(char) * 1024);
free(p);
free(p);
return 0;
}
运行结果:
valgrind 同样适用于C++程序,比如new和delete没有匹配使用、堆栈内存越界访问,用法和上面的一样,这里就不写代码了。
内存泄漏问题在定位问题的时候很难被发现,所以这个时候valgrind就可以发挥很大的力量。 同时也要提醒大家,平时写代码的时候也要注意,申请了内存用完后一定要释放,就像去图书馆借书一样,光借不还,图书馆迟早要关门。 养成良好的编码习惯,毕竟调试程序也很废脑子。