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++知识库 -> 内存泄漏浅析 -> 正文阅读

[C++知识库]内存泄漏浅析

内存泄漏

核心:什么时候有,在哪个地方

基本判断方式

1.htop/top;肉眼观察

htop

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yo2rmOi-1652670917559)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402185714665.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QBcCrN1-1652670917561)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402185905910.png)]

(1)CPU状态区域
(2)整体状态区域
(3)内存状态区域
(4)进程状态区域
(5)管理控制区域

top

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPePI02E-1652670917562)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402190227630.png)]

2.mtrace

使用方式:对原文件加上-g

gcc -g test.c
export MALLOC_TRACE=a.log 
./a.out 
unset MALLOC_TRACE #记得执行完后unset变量,否则可能运行其他命令可能覆盖log 
mtrace a.out a.log 

测试用例

#include <stdio.h>
 
int main()
{
        setenv("MALLOC_TRACE", "lxc.log", "1");
        mtrace();
 
        int *p = (int *)malloc(2 * sizeof(int));
 
        return 0;
}

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2UUZ2tC-1652670917563)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402191821962.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E8CVJOuI-1652670917563)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402191912056.png)]

位置判断

malloc没有free,new没有delete的地方

一般只适用于离线测试,在线测试不考虑

1.文件调用自己的malloc和free

//每次你怀疑有内存泄漏,就if 1打开
#if 1
void* _malloc(size_t size,const char* file,int line){
    void *p =malloc(size);//正常分配,p表示分配地址

    // printf("malloc[+]:%p --->%s:%ld",p,file,line);
    char buff[128] = {0};
    sprintf(buff,"./mem/%p.mem",p);//把当前文件的地址作为文件名写入buff
    
    FILE* fp = fopen(buff,"w");//打开文件

    fprintf(fp,"[%s:%d]--->addr:%p,size:%ld",file,line,p,size);//写入文件
    fflush(fp);	
    fclose(fp);
    return p;
}

void* _free(void* p,const char* file,int line){
    char buff[128] = {0};
    sprintf(buff,"./mem/%p.mem",p);//故技重施,把p名字指向的文件地址给buff
    //unlink应该就是减少一个文件指针,如果返回值<0说明这个文件本身就已经没有引用且被free过了
    if(unlink(buff) < 0){
        //执行unlink()函数会删除所给参数指定的文件
        //如果不是1说明此文件还有其他链接对象,因此只对此文件的连接数进行减1操作
        //若连接数为1,并且在此时没有任何进程打开该文件,此内容才会真正地被删除掉
        //返回值:成功返回0,失败返回 -1
        printf("double free.\n",p);
        return NULL;
    }
    free(p);
    
}


#define malloc(size) _malloc(size,__FILE__,__LINE__);
#define free(size) _free(size,__FILE__,__LINE__);
//__FILE__:宏定义,调用文件名;__LINE__:宏定义,调用行
//如果你的宏定义在你自己的函数声明之前,那就不能再你自己的代码里调用系统的malloc函数了

//你每次调用自己的,就会递归的调用被你改定义的新函数,就会死循环
//所以声明在实现后面,因为你调用的是改声明之前的同名函数
#endif


void func(void){
    void *p1 = malloc(10);
    void *p2 = malloc(20);//不free就删不掉文件
    free(p1);

    void *p3 = malloc(30);
    free(p3);
    return;
}
int main(){
    func();
    return 0;
}
//cd experment/Backend/memory_leak/singleMode
//gcc -o singleFile singleFile.c
// ./singleFile

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WutnzaJs-1652670917564)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402192724666.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wIKVxrTf-1652670917565)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402192733804.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M0SdzhZL-1652670917565)(C:\Users\8208191402\AppData\Roaming\Typora\typora-user-images\image-20220402192742412.png)]

运行一次出一个这样的文件,对应程序里的p2

限制

你用了宏定义,这只适用于单个文件,因为加载时会有第三方so库,多文件不可以用

2.对malloc和free函数做一个hook(dlsym)

固定格式:定义一个与原函数同一回调类型的函数,做hook用

typedef void*(*malloc_t)(size_t size);//新的函数指针类型
malloc_t malloc_f = NULL;//一个该类型的指针变量

typedef void(*free_t)(void* p);
free_t free_f = NULL;

对空函数指针初始化:

static void init_hook(){
    if(malloc_f == NULL){
        malloc_f = dlsym(RTLD_NEXT,"malloc");
    }
    if(free_f == NULL){
        malloc_f = dlsym(RTLD_NEXT,"malloc");
    }
}

init_hook()操作放在main函数入口的地方(第一行);不用的时候把第一行注释掉就行

#define DEBUG_MEMLEAK init_hook();
int main(){
    DEBUG_MEMLEAK;
    func();
    return 0;
}

具体两个函数的覆写内容和前面完全一样,但是函数名和系统函数名一样了,里面调用的不再是系统函数,而是你定义的函数指针。

为了防止递归调用,还需要一个全局变量,确保只有第一次进入的时候执行你覆写的函数,其他的不会递归执行

保证你的函数不可重入

完整代码

#define _GNU_SOURCE         
/* 开启GNU特殊性质的,也可以理解为打开dlgcn.h里功能的 */
/* 你在设计模式里也用过了,ifdef.....里面的可以选择执行的...endif */
/* 这必须是您的第一个预处理器指令,在你要启动文件前面声明,相当于文件开关*/
#include<dlfcn.h> 
#include<stdio.h>
#include<stdlib.h>

#include<unistd.h>

        
//hook:定义一个和malloc同一回调类型的函数
typedef void*(*malloc_t)(size_t size);//函数指针
malloc_t malloc_f = NULL;

typedef void(*free_t)(void* p);
free_t free_f = NULL;


int enable_hook_malloc = 1;
int enable_hook_free = 1;
//防止在你覆写的malloc函数里递归调用malloc函数(例如这里的fopen会调用系统的free),导致文件引用计数不一致
//因为有的调用malloc可能会会自己free,这样你再free就double
void* malloc(size_t size){
    if(enable_hook_malloc){
    	enable_hook_malloc = 0;
    void *p = malloc_f(size);

    void* caller = __builtin_return_address(0);//返回上一级调用点
    //别打错了
    //__FILE__和__LINE__只能在宏定义中,函数调用不能使用,所要用这个找到调用处,
    //0表示上一层,1表示上上层,以此类推

    char buff[128] = {0};
    sprintf(buff,"./mem/%p.mem",p);
    
    FILE* fp = fopen(buff,"w");

    fprintf(fp,"[+%p]--->addr:%p,size:%ld",caller,p,size);
    fflush(fp);	
    fclose(fp);
        enable_hook_malloc=1;
    }else{
        return malloc_f(size);
    }
    return p;
}

void free(void* p){
    if(enable_hook_free){
        enable_hook_free=0;
    char buff[128] = {0};
    sprintf(buff,"./mem/%p.mem",p);
    if(unlink(buff) < 0){
        printf("double free.\n",p);
        enable_hook_free=1;
        return;
    }
    free_f(p);
        enable_hook_free=1;
    }else{
        free_f(p);
    }
    return;
}

static void init_hook(){
    if(malloc_f == NULL){
        malloc_f = dlsym(RTLD_NEXT,"malloc");
    }
    if(free_f == NULL){
        malloc_f = dlsym(RTLD_NEXT,"malloc");
    }
}

#define DEBUG_MEMLEAK init_hook();


void func(void){
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    free(p1);

    void *p3 = malloc(30);
    free(p3);
    return;
}

int main(){
    DEBUG_MEMLEAK;
    func();
    return 0;
}
//cd experment/Backend/memory_leak/multiMode
//gcc -o mem_hook mem_hook.c     这样就会undefied reference to dlsym
//gcc -o mem_hook mem_hook.c -ldl
// ./mem_hook

//dlsym 报错,就查看怎么用这个:man dlsym

p3);
return;
}

int main(){
DEBUG_MEMLEAK;
func();
return 0;
}
//cd experment/Backend/memory_leak/multiMode
//gcc -o mem_hook mem_hook.c 这样就会undefied reference to dlsym
//gcc -o mem_hook mem_hook.c -ldl
// ./mem_hook

//dlsym 报错,就查看怎么用这个:man dlsym


  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-06-01 15:00:47  更:2022-06-01 15:01:39 
 
开发: 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年11日历 -2024/11/23 16:40:38-

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