IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> [Linux段错误 :C/C++]一种内存泄漏的排查方法 -> 正文阅读

[C++知识库][Linux段错误 :C/C++]一种内存泄漏的排查方法


前言

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。本文不再过多介绍内存泄漏的分类,注重介绍一种如何解决定位,内存泄漏的代码处。


提示:以下是本篇文章正文内容,下面案例可供参考

一、如何引起内存泄漏?

C/C++代码开发时:通常是由于动态申请内存没有释放引起的
比如:mallo完后没有freenew 完后没有及时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下查看内存命令:

  • 通常使用top命令来查看进程运行时的虚拟内存。
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),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行:
    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 1224 18:54 0x7ba7a0.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba7c0.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba7e0.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba800.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba820.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba840.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba860.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba880.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba8a0.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba8c0.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba8e0.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba900.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba920.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba940.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba960.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba980.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba9a0.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7ba9c0.mem
-rw-r--r-- 1 root root    36 1224 18:54 0x7ba9e0.mem
-rw-r--r-- 1 root root    37 1224 18:54 0x7baa00.mem
-rwxr--r-- 1 root root  1161 1224 18:55 main.c
-rwxr-xr-x 1 root root 16984 1224 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就解决了,是不是很开心呢?

总结

例如:以上就是今天要讲的内容,本文仅仅简单介绍了一种通过写文件的方式定位内存泄漏的地方,每个人的方法都不一样,定位问题的方式也不尽相同。但是肯定比观察法更靠谱吧,眼睛看是很难看出问题的。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:47:31  更:2022-12-25 10:47:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 7:07:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码