【C/C++】什么是内存泄漏?如何检测内存泄漏?

一、内存泄漏概述

1.1 什么是内存泄漏

内存泄漏是在没有自动 gc 的编程语言里面,经常发生的一个问题。

自动垃圾回收(Automatic Garbage Collection,简称 GC)是一种内存管理技术,在程序运行时自动检测和回收不再使用的内存对象,以避免内存泄漏和释放已分配内存的负担。

因为没有 gc,所以分配的内存需要程序员自己调用释放。其核心原因是调用分配与释放没有符合开闭原则,没有配对,形成了有分配,没有释放的指针,从而产生了内存泄漏。

void func(size_t s1)
{void p1=malloc(s1);void p2=malloc(s1);free(p1);
}

以上代码段,分配了两个s1大小的内存块,由 p1 与 p2 指向。而代码块执行完以后,释放了 p1,而 p2 没有释放。形成了有分配没有释放的指针,产生了内存泄漏。

1.2 内存泄漏导致的后果

随着工程代码量越来越多,有分配没有释放,自然会使得进程堆的内存会越来越少,直到耗尽。从而导致后面的运行时代码不能成功分配内存,使程序奔溃。

1.3 内存泄漏解决思路

最好的办法肯定是引入自动垃圾回收gc。但是这不适合C/C++语言。

解决内存泄漏,我们需要解决两点:

1)能够检测出来是否发送内存泄漏

2)如果发生内存泄漏,能够检测出来具体是哪一行代码所引起的。

内存泄漏是由于内存分配与内存释放,不匹配所引起的。因此对内存分配函数malloc/calloc/realloc,以及内存释放函数free进行“劫持”hook,就能能够统计出内存分配的位置,内存释放的位置,从而判断是否匹配。

二、宏定义方法

2.1 宏定义

使用宏定义,替换系统的内存分配接口。并利用__FILE__、__LINE__分别获取当前编译文件的文件名、行号,进行追踪位置信息。

#define malloc(size)    _malloc(size, __FILE__, __LINE__)
#define free(ptr)       _free(ptr, __FILE__, __LINE__)

需要注意的是,宏定义一定要放在内存分配之前,这样预编译阶段才会替换为我们自己实现的_malloc和_free。

2.2 检测位置

为了方便观察,我们可以在内存分配_malloc的时候,创建一个文件。文件名为指向新分配内存的指针值,文件内容为指针值、调用_malloc时的文件名、行号。

在该内存释放_free的时候,删除该指针对应的文件。

最后,程序运行结束,如果没有文件说明没有内存泄漏,否则说明存在内存泄漏。

2.3 结果分析

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>void *_malloc(size_t size, const char *filename, int line){void *ptr = malloc(size);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");fprintf(fp, "[+]addr: %p, filename: %s, line: %d\n", ptr, filename, line);fflush(fp);fclose(fp);return ptr;
}void _free(void *ptr, const char *filename, int line){char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}return free(ptr);
}#define malloc(size)    _malloc(size, __FILE__, __LINE__)
#define free(ptr)       _free(ptr, __FILE__, __LINE__)int main() {void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);}

最后在memory文件夹里,可以看到存在一个文件,说明有一个地方出现内存泄漏

[+]addr: 0x559e55b6e8b0, filename: fun1.c, line: 39

从结果上看,内存泄漏发生第39行。

相关视频推荐

c/c++后端服务器开发必学基础组件设计与实现,实操+手写代码,2024跳槽涨薪必备教程!内容包含(线程池、内存池、多线程锁、死锁检测、内存泄漏、分布式锁..)icon-default.png?t=N7T8https://www.bilibili.com/video/BV11Z421v7Vx/

免费学习地址:Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)

需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

三、hook方法

利用 hook 机制改写系统的内存分配函数。

3.1 hook

hook方法的实现分三个步骤

1)定义函数指针。

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;typedef void (*free_t)(void *ptr);
free_t free_f = NULL;

2)函数实现,函数名与目标函数名一致。

void *malloc(size_t size)
{//改写的功能
}void free(void *ptr)
{//改写的功能
}

3)初始化hook,调用dlsym()。

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

3.2 检测位置

宏定义的方法在检测调用所在行号的时候使用了系统定义的__LINE__,因为是宏定义的malloc,预编译时候直接嵌入。因此__LINE__返回的就是调用malloc的位置。

但是hook方法不一样,系统定义的__LINE__在函数内部调用,无法确定在主函数中的调用位置。比如

fprintf(fp, "[+]addr: %p, filename: %s, line: %d\n", ptr, filename, line);

返回的就是fprintf所在的行号。

因此使用gcc 提供的__builtin_return_address,该函数返回当前函数或其调用者之一的返回地址。 参数level 表示向上扫描调用堆栈的帧数。比如对于 main --> f1() --> f2() --> f3() ,f3()函数里面调用 __builtin_return_address (0),返回f3的地址;调用 __builtin_return_address (1),返回f2的地址;

3.3 递归调用

hook的时候,要考虑其他函数也用到所hook住的函数,比如在printf()函数里面也调用了malloc,那么就需要防止内部递归进入死循环。

通过gdb调试,在第23行打断点,发现每次运行都回到了23行。

这是因为sprintf隐含调用了malloc,这样就陷入一个循环:

23行的sprintf —> 自定义的malloc —> 23行的sprintf —> 自定义的malloc --> 23行的sprintf —> 自定义的malloc --> ……

解决办法是,限制调用次数。当进入 malloc 函数内部后,根据自己的需要,设置 hook 的开关。在关闭的区域内调用 malloc 后进入到 else 部分执行原来的 hook 函数,避免了无限递归的发生。

int enable_malloc_hook = 1;
void *malloc(size_t size) { // 执行改写的 malloc 函数if (enable_malloc_hook) {enable_malloc_hook = 0;// 关闭 hook, printf 内部的 malloc 执行 else 的部分// 其他代码enable_malloc_hook = 1;}// 执行原来的 malloc 函数else {p = malloc_f(size);}
}

3.4 结果分析

// gcc -o fun2 fun2.c -ldl -g#define _GNU_SOURCE
#include <dlfcn.h>#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <link.h>typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;typedef void (*free_t)(void *ptr);
free_t free_f = NULL;int enable_malloc_hook = 1;
int enable_free_hook = 1;void *malloc(size_t size){void *ptr = NULL;if (enable_malloc_hook ){enable_malloc_hook = 0; enable_free_hook = 0;ptr = malloc_f(size);void *caller = __builtin_return_address(0);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");fprintf(fp, "[+] caller: %p, addr: %p, size: %ld\n", caller, ptr, size);fflush(fp);fclose(fp);enable_malloc_hook = 1;enable_free_hook = 1;}else {ptr = malloc_f(size);}return ptr;
}void free(void *ptr){if (enable_free_hook ){enable_free_hook = 0;enable_malloc_hook = 0;char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}free_f(ptr);enable_malloc_hook = 1;enable_free_hook = 1;}else {free_f(ptr);}
}void init_hook(){if (!malloc_f){malloc_f = dlsym(RTLD_NEXT, "malloc");}if (!free_f){free_f = dlsym(RTLD_NEXT, "free");}
}
int main(){init_hook();void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);}

从结果看存在一个内存泄漏,但是 caller:0x16bb 是地址,不是具体行号。使用addr2line可以将地址转换为文件名和行号。

3.5 addr2line

利用addr2line工具,将地址转换为文件名和行号,得到源文件的行数(根据机器码地址定位到源码所在行数)

addr2line -f -e fun2 -a 0x16bb

参数: -f:显示函数名信息。 -e filename:指定需要转换地址的可执行文件名。 -a address:显示指定地址(十六进制)。

但是,高版本 gcc 下使用 addr2line 命令会出现乱码问题。

??
??:0

addr2line 作用于 ELF 可执行文件,而高版本的 gcc 调用 __builtin_return_address返回的地址 caller 位于内存映像上,所以会产生乱码。

解决办法是利用动态链接库的dladdr函数 ,作用于共享目标,可以获取某个地址的符号信息。使用该函数可以解析符号地址

// gcc -o fun2 fun2.c -ldl -g#define _GNU_SOURCE
#include <dlfcn.h>#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <link.h>// 解析地址
void* converToELF(void *addr) {Dl_info info;struct link_map *link;dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP);// printf("%p\n", (void *)(size_t)addr - link->l_addr);return (void *)((size_t)addr - link->l_addr);
}typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;typedef void (*free_t)(void *ptr);
free_t free_f = NULL;int enable_malloc_hook = 1;
int enable_free_hook = 1;void *malloc(size_t size){void *ptr = NULL;if (enable_malloc_hook ){enable_malloc_hook = 0; ptr = malloc_f(size);void *caller = __builtin_return_address(0);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");// converToELF(caller)fprintf(fp, "[+] caller: %p, addr: %p, size: %ld\n", converToELF(caller), ptr, size);fflush(fp);fclose(fp);enable_malloc_hook = 1;}else {ptr = malloc_f(size);}return ptr;
}void free(void *ptr){if (enable_free_hook ){enable_free_hook = 0;char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}free_f(ptr);enable_free_hook = 1;}else {free_f(ptr);}
}void init_hook(){if (!malloc_f){malloc_f = dlsym(RTLD_NEXT, "malloc");}if (!free_f){free_f = dlsym(RTLD_NEXT, "free");}
}
int main(){init_hook();void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);}

四、__libc_malloc 和 __libc_free

思路和hook的一样,因为malloc和free底层调用的也是__libc_malloc和__libc_free。

// gcc -o fun3 fun3.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <link.h>void* converToELF(void *addr) {Dl_info info;struct link_map *link;dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP);// printf("%p\n", (void *)(size_t)addr - link->l_addr);return (void *)((size_t)addr - link->l_addr);
}extern void *__libc_malloc(size_t size);
extern void *__libc_free(void *ptr);int enable_malloc_hook = 1;
int enable_free_hook = 1;void *malloc(size_t size){void *ptr = NULL;if (enable_malloc_hook ){enable_malloc_hook = 0; enable_free_hook = 0;ptr = __libc_malloc(size);void *caller = __builtin_return_address(0);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");fprintf(fp, "[+] caller: %p, addr: %p, size: %ld\n", converToELF(caller), ptr, size);fflush(fp);fclose(fp);enable_malloc_hook = 1;enable_free_hook = 1;}else {ptr = __libc_malloc(size);}return ptr;
}void free(void *ptr){if (enable_free_hook ){enable_free_hook = 0;enable_malloc_hook = 0;char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}__libc_free(ptr);enable_malloc_hook = 1;enable_free_hook = 1;}else {__libc_free(ptr);}
}int main(){void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/818698.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL8.0.36-社区版:错误日志(3)

mysql有个错误日志&#xff0c;是专门记录错误信息的&#xff0c;这个功能默认是开启的 一般都是在/var/log/mysqld.log 日志中存放 1.错误日志的位置 首先我们使用命令去查看一下&#xff0c;这个错误日志文件究竟在哪 进入到mysql中&#xff0c;使用命令 show variables…

FME学习之旅---day24

我们付出一些成本&#xff0c;时间的或者其他&#xff0c;最终总能收获一些什么。 高级地理数据库 教程&#xff1a;地理数据库转换 上述教程包括 如何使用 Esri 模板地理数据库 该内容在FME学习之旅day19 已经学习过 使用地理数据库属性域&#xff1a;编写编码属性域 属…

机器学习实训 Day1(线性回归练习)

线性回归练习 Day1 手搓线性回归 随机初始数据 import numpy as np x np.array([56, 72, 69, 88, 102, 86, 76, 79, 94, 74]) y np.array([92, 102, 86, 110, 130, 99, 96, 102, 105, 92])from matplotlib import pyplot as plt # 内嵌显示 %matplotlib inlineplt.scatter…

古月·ROS2入门21讲——学习笔记(一)核心概念部分1-14讲

讲解视频地址&#xff1a;1.ROS和ROS2是什么_哔哩哔哩_bilibili 笔记分为上篇核心概念部分和下篇常用工具部分 下篇&#xff1a;古月ROS2入门21讲——学习笔记&#xff08;二&#xff09;常用工具部分15-21讲-CSDN博客 目录 第一讲&#xff1a;ROS/ROS2是什么 1. ROS的诞生…

Python-GEE遥感云大数据分析、管理与可视化及多领域案例实践应用

随着航空、航天、近地空间遥感平台的持续发展&#xff0c;遥感技术近年来取得显著进步。遥感数据的空间、时间、光谱分辨率及数据量均大幅提升&#xff0c;呈现出大数据特征。这为相关研究带来了新机遇&#xff0c;但同时也带来巨大挑战。传统的工作站和服务器已无法满足大区域…

海外代理IP在跨境电商中发挥什么作用?

在我国跨境电商的发展中&#xff0c;海外代理IP的应用日益广泛&#xff0c;它不仅帮助商家成功打入国际市场&#xff0c;还为他们在多变的全球电商竞争中保持优势。下面是海外代理IP在跨境电商中五个关键的应用场景。 1、精准的市场分析 了解目标市场的消费者行为、产品趋势以…

金蝶云星空与泛微OA对接案例-实现流程一体化

摘要&#xff1a;在企业数字化中&#xff0c;集成内部各业务系统以实现数据共享与流程协同&#xff0c;已成为提升运营效率、优化决策的重要选择。本文将以某企业成功实现金蝶云星空与泛微OA系统对接为例&#xff0c;详细解析双方在人员信息、组织架构、销售合同、付款申请、报…

快速探索随机树-RRT

文章目录 简介原理算法运动规划的变体和改进简介 快速探索随机树(RRT)是一种算法,旨在通过随机构建空间填充树来有效搜索非凸高维空间。该树是从搜索空间随机抽取的样本中逐步构建的,并且本质上偏向于向问题的大型未搜索区域生长。RRT 由 Steven M. LaValle 和 James J. K…

面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!

时间片 超线程 上下文切换 切换查看 线程调度 引起线程上下文切换的因素 由于现在大多计算机都是多核CPU&#xff0c;多线程往往会比单线程更快&#xff0c;更能够提高并发&#xff0c;但提高并发并不意味着启动更多的线程来执行。更多的线程意味着线程创建销毁开销加大、…

python数据结构与算法之线性表

1、线性表 是一种由n个元素&#xff08;n> 0 &#xff09;数据元素组成的有限序列&#xff0c;所包含的元素数量通常被称为表的长度 n 0 的表被称为空表&#xff0c;线性表的数据元素可以单一也可以复杂&#xff0c;可以是整数&#xff0c;字符串&#xff0c;也可以是由几…

进程间通信--管道

1.有名管道 管道的分类:有名管道和无名管道 有名管道也成为命名管道.区别:有名管道在任意两个进程之间通信,无名管道在父子进程之间通信. 1.创建有名管道使用命令:mkfifo 2.打开管道:open(); 关闭管道:close(); 读数据:read(); 写入数据:write(); 2.有名管道来演示进程间通信:…

高光谱遥感数据处理与机器学习深度应用

高光谱遥感数据处理的基础、python开发基础、机器学习和应用实践。重点解释高光谱数据处理所涉及的基本概念和理论&#xff0c;旨在帮助学员深入理解科学原理。结合Python编程工具&#xff0c;专注于解决高光谱数据读取、数据预处理、高光谱数据机器学习等技术难题&#xff0c;…

将自己的项目上传至Git

一、安装Git 官网:Git (git-scm.com) 二、注册gitee 官网:工作台 - Gitee.com 进入“我的”出现以下界面 三、创建仓库 点击加号&#xff0c;新建仓库 根据自己的需求取名&#xff0c;描述仓库&#xff0c;开源还是私有&#xff0c;点击创建即可&#xff0c;点击我的即可…

[docker] 核心知识 - 容器/镜像的管理和操作

[docker] 核心知识 - 容器/镜像的管理和操作 想要查看完整的指令&#xff0c;可以通过 docker --help 列举所有的指令&#xff0c;这里会提到一些比较常用的核心指令 查看容器的状态 这个应该是最常用的指令&#xff0c;语法为 docker ps&#xff0c; ps 为 process status …

RT-Thread时钟管理

操作系统需要通过时间来规范其任务,主要介绍时钟节拍和基于时钟节拍的定时器。 时钟节拍 任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。 RT-Thread 中,时钟节拍的长度可以根据 RT_TICK_P…

租用马来西亚服务器:稳定高效的网络选择

马来西亚首都是吉隆坡。作为一个新兴的多元化经济国家&#xff0c;也属于亚洲四小龙之一。地理位置优越&#xff0c;中间隔着南中国海。一部分是北接泰国的位于马来半岛的西马来西亚&#xff0c;另一部分则是东马来西亚&#xff0c;在婆罗洲岛的北部。这种地理位置有利于促进该…

IGBT退饱和现象解析与防范

IGBT是一种重要的功率半导体器件&#xff0c;广泛应用于电力电子领域&#xff0c;如变频器、电动机驱动、电力传输等。在这些应用中&#xff0c;IGBT的导通和关断特性至关重要&#xff0c;而退饱和是IGBT工作过程中的一个重要现象。 IGBT的退饱和定义 退饱和是指IGBT在导通状态…

WordPress用户福音:Elementor Pro国产版替代方案,全新中文界面更懂你

如果你正在考虑创建自己的网站&#xff0c;那么在第一次谷歌搜索时&#xff0c;你可能已经看到了WordPress、Elementor和网站构建器这些专业名称。WordPress是最受欢迎的网站平台之一&#xff0c;这不难理解&#xff1a;它高度可定制&#xff0c;易于学习&#xff0c;而且是免费…

java算法day55 | 动态规划part16 ● 583. 两个字符串的删除操作 ● 72. 编辑距离

583. 两个字符串的删除操作 思路&#xff1a; 和1143.最长公共子序列这道题思路相同&#xff0c;只不过需要对return的数据做一些操作。 class Solution {public int minDistance(String word1, String word2) {int[][] dpnew int[word1.length()1][word2.length()1];for(int …

【Linux 驱动基础】设备树中断

# 前置知识 interrupts 文档 Specifying interrupt information for devices 1) Interrupt client nodes -------------------------Nodes that describe devices which generate interrupts must contain an "interrupts" property, an "interrupts-extende…