前言
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。本文不再过多介绍内存泄漏的分类,注重介绍一种如何解决定位,内存泄漏的代码处。
提示:以下是本篇文章正文内容,下面案例可供参考
一、如何引起内存泄漏?
C/C++代码开发时:通常是由于动态申请内存没有释放引起的 比如:mallo完后没有free,new 完后没有及时delete。
注意:为了避免出现内存泄漏,malloc,free。new,delete 都是成对存在的。
二、为何判断是否为内存泄漏?
查看内存
查看内存是最基本的方法,也是最直接的方法。因为内存泄漏引起的就是虚拟内存的增长。 注意:内存上涨不一定就是内存泄漏引起的!,要根据实际业务,也许内存上涨本身就是程序需要的,所以内存上涨了不一定就是内存泄漏了哦,这点需要注意。
我们先手写一个内存泄漏的代码main.c(示例):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(){
while(1){
void *p1 = malloc(5);
void *p2 = malloc(10);
free(p1);
usleep(100);
}
}
简单直接的while循环申请了两块空间,分别是5个字节与10个字节。分别用
指针p1与p2去接收。p1申请了后free释放了,但是 p2我们并没有释放,这就
会引起内存泄漏。
编译运行一下
yangshiqiang@uos20amd64:/home/debug# gcc -o mem main.c
yangshiqiang@uos20amd64:/home/debug# ls
main.c mem
yangshiqiang@uos20amd64:/home/debug#
yangshiqiang@uos20amd64:/home/debug# gcc -o mem main.c
yangshiqiang@uos20amd64:/home/debug# ls
main.c mem
yangshiqiang@uos20amd64:/home/debug# ./mem
注意:在一些小的程序里面,我们申请了内存也就是malloc了并没有释放,程序运行也并没有出现段错误,那是因为你每次申请的内存都比较小或者说频率很低。比如:申请4个字节,程序一天下来也才总共申请几十上百字节,显然这种情况,系统内存空间足够允许你有这样的失误。但是如果上线的服务器中申请空间大频率高的话。24X7小时不间断的运行,将产生巨大的内存。这时候内存泄漏将很快导致你服务器崩溃,而你要知道服务器是不能出现这样的意外的。你可以想象你正在在王者荣耀突然游戏崩了,你会是什么样的心情呢?
查看内存命令
Linux下查看内存命令:
B Mem : 3923.4 total, 696.3 free, 970.0 used, 2257.2 buff/cache
MiB Swap: 3071.0 total, 3071.0 free, 0.0 used. 2663.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
16975 root 20 0 3816 2944 1324 S 57.3 0.1 0:10.80 mem
770 message+ 20 0 8164 5608 3600 S 38.7 0.1 0:55.82 dbus-dae+
1431 root 20 0 132976 13372 12364 S 37.7 0.3
VIRT 字段即为程序运行时的虚拟内存大小。
由于系统运行程序过多使用top会显示所以正在运行的程序且一直在滚动,所以我们可以使用:top |grep 进程名。这样就只关注我们需要的进程了。
top |grep xxx命令
yangshiqiang@uos20amd64:/home/debug# top |grep mem
15790 yangshiqiang 20 0 4456 3564 1300 S 35.3 0.1 0:07.44 mem
15790 yangshiqiang 0 4840 3948 1300 R 34.6 0.1 0:08.48 mem
15790 yangshiqiang 20 0 5096 4204 1300 R 37.3 0.1 0:09.60 mem
可以看出mem进程的内存一直在上涨
如何定位代码中的内存泄漏?
我们知道,malloc与free是成对存在的,定义一个全局的计数变量:global_memcount;初始为0.如果malloc看出+1的话,那么free就为-1。因此我们只需要判断最终global_memcount的值为零则说明内存安全,反之则为内存泄漏。
使用宏替换mallo与free去执行我们的计数(示例):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
int global_memcount = 0;
void *_malloc(size_t size){
global_memcount ++;
printf("_malloc\n");
}
void _free(void *ptr){
global_memcount --;
printf("_free\n");
}
#define malloc(size) _malloc(size)
#define free(ptr) _free(ptr)
int main(){
while(1){
void *p1 = malloc(5);
void *p2 = malloc(10);
free(p1);
usleep(100);
assert(global_memcount == 0);
}
}
运行结果:
yangshiqiang@uos20amd64:/home/debug# ./mem
_malloc
_malloc
_free
test: main.c:48: main: Assertion `global_memcount == 0' failed.
已放弃
yangshiqiang@uos20amd64:/home/debug# vi main.c
yangshiqiang@uos20amd64:/home/debug#
显示global_memcount 最终不为0,那么我们可以确定
代码中出现了内存泄漏了,我们现在可以很肯定了。
这段简单的代码中我们肉眼很明显可以看到是因为
void *p2 = malloc(10);这一句没有释放
但是如果代码文件很大的情况下我们又如何快速定位代码哪一行了内存泄漏呢?
写文件
核心思想:通过写文件的方式,malloc的时候创建一个文件名并写入,malloc代码的行数,指针地址。free的时候也删除一个相同的文件。这样malloc创建文件。free的时候删除这个文件。最终,如果有一个malloc落单了没有被free,那么就会有一个创建的文件并没有并删除掉。需要用到unlink函数
unlink函数说明
【 unlink系统调用】
功能描述:
从文件系统中删除一个名称。如果名称是文件的最后一个连接,并且
没有其它进程将文件打开,名称对应的文件会实际被删除。
用法:
#include <unistd.h>
int unlink(const char *pathname);
参数:
pathname:指向需解除连接的文件名。
返回说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EACCES:权能不足
EFAULT: 内存空间不可访问
EIO:发生输入输出错误
EISDIR:pathname索引的是目录
ELOOP :路径解析的过程中存在太多的符号连接
ENAMETOOLONG:路径名超出可允许的长度
ENOENT:路径名部分内容表示的目录不存在,或者是悬浮的连接
ENOMEM: 核心内存不足
ENOTDIR:路径名的部分内容不是目录
EPERM : 文件系统不支持文件或者目录的解除连接,也有可能是权限步允许
EROFS :文件系统只读
更改代码如下(示例):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
void *_malloc(size_t size, const char *filename, int line){
void *p = malloc(size);
char fileName[128] = {0};
sprintf(fileName,"./%p.mem",p);
FILE *fp = fopen(fileName,"w");
fprintf(fp,"[+]%s:%d,addr: %p size: %ld\n",filename, line, p, size);
fflush(fp);
fclose(fp);
return p;
}
void _free(void *ptr, const char *filename, int line){
char fileName[128] = {0};
sprintf(fileName,"./%p.mem",ptr);
if(unlink(fileName)<0){
printf(" double free\n");
return ;
}
return free(ptr);
}
#define malloc(size) _malloc(size, __FILE__, __LINE__)
#define free(ptr) _free(ptr, __FILE__, __LINE__)
int main(){
while(1){
void *p1 = malloc(5);
void *p2 = malloc(10);
free(p1);
usleep(100);
}
}
运行结果:
rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba7a0.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba7c0.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba7e0.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba800.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba820.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba840.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba860.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba880.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba8a0.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba8c0.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba8e0.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba900.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba920.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba940.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba960.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba980.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba9a0.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7ba9c0.mem
-rw-r--r-- 1 root root 36 12月 24 18:54 0x7ba9e0.mem
-rw-r--r-- 1 root root 37 12月 24 18:54 0x7baa00.mem
-rwxr--r-- 1 root root 1161 12月 24 18:55 main.c
-rwxr-xr-x 1 root root 16984 12月 24 18:55 mem
可以看到产行了大量的文件,这就是没malloc一次的时候创建了一个文件,我们cat查看问了内容
yangshiqiang@uos20amd64:/home/debug# cat 0x7baa00.mem
[+]main.c:34,addr: 0x7baa00 size: 10
我们可以看到:在main.c文件中第34行代码处,地址为:0x7baa00 ,内存大小为:10个字节。
那么我们的bug就解决了,是不是很开心呢?
总结
例如:以上就是今天要讲的内容,本文仅仅简单介绍了一种通过写文件的方式定位内存泄漏的地方,每个人的方法都不一样,定位问题的方式也不尽相同。但是肯定比观察法更靠谱吧,眼睛看是很难看出问题的。
|