关于malloc/free的一些知识点

关于malloc/free,我们都不陌生,在最开始学习c语言时就相当了解,包括c++中的new也是封装的malloc。下边我以glibc实现的malloc来讲述一些关于malloc/free的知识点。

malloc/free

  • malloc和free并不是系统调用,而是运行时库(eg.libc.so)的api,而运行时库又会去调用系统调用从操作系统申请内存,这里的系统调用在Linux上一般是brk/sbrk或者mmap。
  • 一般来讲当调用malloc时,申请下来的内存除了返回给调用者外,还有一部分作为元数据,这里的元数据会存储malloc给用户的内存大小,所以当调用free释放内存时,只需要传递指针,运行时库自然就知道要释放多少内存。
  • free调用后,运行时库一般来说并不会立即释放内存给操作系统,而是先将这块内存放到一个内存池中,等待下一次malloc时再从内存池中取出。
int main() {{std::list<int> ll;for (int i = 0; i < 100000000; ++i) {ll.push_back(i);}}for (;;) ;return 0;
}

然后看下top,其实list已经被释放了,但是这个进程的内存还是存在,或者说并没有完全释放:

> top -p 91376
PID       %CPU TIME      MEM    
91376     93.4 01:31.06 7420K

mallopt

该函数针对于malloc进行的一些配置,设置malloc内部的一些参数大小
首先我们简单了解下glibc的ptmalloc的实现逻辑:
ptmalloc中组织内存的数据结构为:

struct malloc_chunk {INTERNAL_SIZE_T      mchunk_prev_size;INTERNAL_SIZE_T      mchunk_size;struct malloc_chunk* fd; struct malloc_chunk* bk;struct malloc_chunk* fd_nextsize;struct malloc_chunk* bk_nextsize;
};

包括分配给用户是同样指向一个chunk,mchunk_prev_sizemchunk_size表示上一个chunk的大小和当前chunk的大小之和,malloc函数返回的指针指向fd这个位置,只有当chunk被释放时,以下的四个字段才会被使用在ptmalloc内部被组织使用,纳入到空闲链表等。

chunk被以下数据结构来组织,malloc_state也被称为是内存区,glibc实现的ptmalloc避免多线程并发引入主分配区和非主分配区,每个进程有一个主分配区,也可以允许有多个非主分配区。主分配区可以使用brk和mmap来分配,而非主分配区只能使用mmap来映射内存块。

struct malloc_state
{__libc_lock_define (, mutex);      //互斥锁int flags;                         //标志 mfastbinptr fastbinsY[NFASTBINS];  //fastbinsmchunkptr top;                     //top chunk mchunkptr bins[NBINS * 2 - 2];     //unsortedbins,smallbins,largebinsunsigned int binmap[BINMAPSIZE];   //bin位图struct malloc_state *next;         //链表指针......
};

当用户调用malloc时,会先去获取一个内存区,然后加锁,在这个内存区上分配内存。加锁失败就会继续找next的内存区,直到找到一个可用的内存区,都不可用,就会创建内存区。超过内存区限制数量,就会循环等待直到找到一个空闲的内存区,进而进入分配流程。

然后我们进一步来看下ptmalloc的内存组织形式:

  1. fastbin – 小块内存的快速分配,fastbin的分配方式就是直接从fastbin链表上取,所以fastbin的分配效率很高,但是fastbin的分配范围比较小,一般不超过128字节。
  2. top就是指向从操作系统申请的内存
  3. bins里有3个链表,分别是unsortedbins,smallbins,largebins。
    当用户释放的内存大于max_fast或者fast bins合并后的chunk都会首先进入unsorted bin上。在unsorted bin中的chunk经过合并处理后到达smallbins及largebins。

    smallbins – 用于存储32-1008字节的chunk。

    largebins – 用于存储大于1024字节的chunk。

    (这里的字节大小可能随glibc的版本及SIZE_SZ 取值不同而变化,供参考)

然后来看下各个参数的含义:

  • M_MXFAST – fastbin范围的最大值,value值在0~20sizeof(void)之间
  • M_TRIM_THRESHOLD – 主内存区的top_chunk的收缩阈值
  • M_TOP_PAD – 控制堆顶部的额外空间。堆顶部额外空间可用于缓解堆碎片的问题。默认值为0
  • M_MMAP_THRESHOLD – malloc通过mmap直接向系统申请内存的临界值,默认128K
  • M_MMAP_MAX 通过mmap分配的内存块个数的上限,默认值为65536
  • M_CHECK_ACTION 用于设置内存错误检查时的处理方式。默认值为2(执行abort) 1 – 打印错误信息
  • M_PERTURB 控制内存分配时填充内存块的内容。默认值为0。

malloc_stats

输出malloc的内存统计信息,我们看下输出是怎样的:

int main() {std::thread t1([](){int* a = new int[1000];});std::thread t2([](){int* a = new int[128 * 1024];});int* a = new int[1000];t1.join();t2.join();malloc_stats();return 0;
}

然后输出是这样的:

Arena 0:
system bytes     =     135168
in use bytes     =      78000
Arena 1:
system bytes     =     135168
in use bytes     =       6256
Total (incl. mmap):
system bytes     =     798720
in use bytes     =     612640
max mmap regions =          1
max mmap bytes   =     528384

大致就是这个进程用了多少内存,Arena是表示内存区的意思。
以上内存统计大概意思是用了两个内存区,system bytes是从操作系统分配的内存大小,in use bytes应该就是分配给用户的正在使用的内存大小。

因为涉及到锁,在多线程的情况下ptmalloc性能不是那么优秀,我们自己也可以试试tcmalloc,这里我使用tcmalloc输出下统计信息:

MALLOC:         623232 (    0.6 MiB) Bytes in use by application
MALLOC: +       393216 (    0.4 MiB) Bytes in page heap freelist
MALLOC: +        31784 (    0.0 MiB) Bytes in central cache freelist
MALLOC: +            0 (    0.0 MiB) Bytes in transfer cache freelist
MALLOC: +          344 (    0.0 MiB) Bytes in thread cache freelists
MALLOC: +      2490432 (    2.4 MiB) Bytes in malloc metadata
MALLOC:   ------------
MALLOC: =      3539008 (    3.4 MiB) Actual memory used (physical + swap)
MALLOC: +            0 (    0.0 MiB) Bytes released to OS (aka unmapped)
MALLOC:   ------------
MALLOC: =      3539008 (    3.4 MiB) Virtual address space used
MALLOC:
MALLOC:             10              Spans in use
MALLOC:              1              Thread heaps in use
MALLOC:           8192              Tcmalloc page size

这是tcmalloc重写了malloc_stats函数的输出,因为实现不一样所以输出也不一样,不过个人看来tcmalloc更加详细一点。

malloc_usable_size

该函数返回使用malloc分配的内存块的可用大小,可能要比malloc传入的参数大,应该是为了做对齐。来看个例子:

int main() {int* a = new int[1000];std::cout << malloc_usable_size(a) << std::endl; // 4008return 0;
}

要比4000大,因为内存对齐,所以多出8个字节

malloc_trim

该函数通过调用sbrk或者madvise来释放内存给操作系统,当你需要显式的释放内存到操作系统时,可以最开始的函数,我们稍微改动下:

int main() {{std::list<int> ll;for (int i = 0; i < 100000000; ++i) {ll.push_back(i);}}malloc_trim(0);for (;;) ;return 0;
}

则可以看到内存已经释放到操作系统了

PID      VIRT   RES   SHR  S COMMAND
96270    20096  3144  2932 R main_prog

参数名字是pad,堆顶部保留不回收的可用空间量,如果该参数为0,则仅在堆顶部保留
最小量的内存。

不过个人认为还是谨慎使用,因为这个就会触发内存回收,及后续相应的系统调用,对于性能来说还是有影响的。

MALLOC_CHECK_

该环境变量可以用来开启内存错误检查,默认是关闭的,开启后,如果内存分配错误,会调用abort,比如调用free一个没有分配的内存,或者调用malloc一个超过内存限制的内存。

  • 0: 默认的,关闭检查
  • 1: 发生错误时,在stderr打印错误信息
  • 2: 发生错误时,调用abort(如果开了core dump,那么会生成core文件)

hook或replace malloc和free函数

有时候我们想要自己来重写malloc和free函数,比如想自己来管理内存,或者在malloc中打印日志什么的,这里提供的有两种方法:

1. __malloc_hook

这是官方提供的hook malloc等函数的方法,在malloc.h的头文件中,已经被标注为废弃的,但是还是可以使用:

/* Hooks for debugging and user-defined versions. */
extern void (*__MALLOC_HOOK_VOLATILE __free_hook) (void *__ptr,const void *)
__MALLOC_DEPRECATED;extern void *(*__MALLOC_HOOK_VOLATILE __malloc_hook)(size_t __size,const void *)
__MALLOC_DEPRECATED;

这是一组函数指针,那也就是说你可以自定义malloc和free函数,这个指针指向你的函数即可:

static void *(*old_malloc_hook)(size_t, const void *);
static void (*old_free_hook)(void *, const void *);static void * my_malloc(size_t size, const void * caller)
{__malloc_hook = old_malloc_hook;void* result = malloc(size);printf("my_malloc return %p, size %d\n", result, (unsigned int)size);__malloc_hook = my_malloc;return result;
}static void my_free(void *ptr, const void *caller)
{__free_hook = old_free_hook;free(ptr);printf("my_free free %p\n", ptr);__free_hook = my_free;
}int main() {__malloc_hook = my_malloc;__free_hook = my_free;void* p1 = malloc(10);free(p1);return 0;
}

为了避免递归调用,我们在进入我们自己hook的malloc函数中首先把hook的指针置空,然后去调用glibc的malloc函数,最后把hook的指针还原。

我这里的例子仅仅是打印一条日志,当然你可以自定义你的函数,比如说重写内存分配方式。

2. LD_PRELOAD及dlsym

  • LD_PRELOAD可以设置一个动态链接库,这个库会被预先加载到进程的地址空间,如果这个库中的符号和malloc重名,那么这个库的函数会被调用,否则就调用glibc的函数。这样起到了替换malloc和free的作用。
  • dlsym是在动态库中寻找符号的一个函数,我们可以使用它来去glibc中去找malloc和free的函数
void* dlsym(void* handle,const char* symbol)

handle表示动态库句柄,symbol表示要寻找的函数名,返回值是函数指针。

handle一般是使用dlopen打开的动态库句柄,除此之外也可以是RTLD_DEFAULT或RTLD_NEXT:

RTLD_DEFAULT表示按默认的顺序搜索共享库中符号symbol第一次出现的地址

RTLD_NEXT表示在当前库以后按默认的顺序搜索共享库中符号symbol第一次出现的地址

所以我们使用RTLD_NEXT就可以获取到glibc中的malloc和free的函数指针,当然你如果是自己实现内存分配则可以不需要获取glibc的函数指针。

这里我们给出一个简单小项目,用来获取一个进程中某个so内存分配的大小:

static void *(*fn_malloc)(size_t size);
static void  (*fn_free)(void *ptr);

声明函数指针去获取glibc的malloc和free函数,到用的时候直接调用:

static void init()
{fn_malloc = (void*(*)(size_t))dlsym(RTLD_NEXT, "malloc");fn_free   = (void(*)(void*))dlsym(RTLD_NEXT, "free");if (!fn_malloc || !fn_free) {fprintf(stderr, "Error in dlsym");exit(1);}
}thread_local bool is_collect_info = false;void *malloc(size_t size)
{if (!fn_malloc) {init();}void *ptr = fn_malloc(size);fprintf(stderr, "allocated bytes memory %ld in %p\n", size, ptr);if (!is_collect_info) {is_collect_info = true;collect_info(ptr, size);is_collect_info = false;}
}

我们这里重写了malloc函数,在函数内部我们判断fn_malloc是否存在,不存在的话先去获取fn_malloc和fn_free的函数指针,然后调用glibc的malloc函数分配空间。最后收集指定so的内存情况。

我们使用is_collect_info变量来控制如果在collect_info发生内存申请的情况,避免递归调用。也就是说
collect_info函数中使用的内存不在统计的范围内。

#define STACK_INFO_LEN  1024
#define MAX_STACK_FRAMES 12void collect_info(void* ptr, size_t size)
{void *p_stack[MAX_STACK_FRAMES];char stack_info[STACK_INFO_LEN * MAX_STACK_FRAMES];char** p_stack_list = nullptr;int frames = backtrace(p_stack, MAX_STACK_FRAMES);p_stack_list = backtrace_symbols(p_stack, frames);if (p_stack_list == nullptr) {return;}for (int i = 0; i < frames; ++i) {if (p_stack_list[i] == nullptr) {break;}if (strstr(p_stack_list[i], dso_name) != nullptr) {mem_size_map[dso_name] += size;mem_pos_map[uint64_t(ptr)] = size;fprintf(stderr, "dso_name %s in %p has size: %ld \n", dso_name, ptr, size);}}

这里我们使用backtracebacktrace_symbols,当前调用栈的符号信息,判断是否包含dso_name(要统计的动态库名),如果包含就使用mem_size_mapmem_pos_map记录内存分配情况。

void free(void *ptr)
{fn_free(ptr);fprintf(stderr, "deallocated bytes memory in %p\n", ptr);if (!is_collect_info && ptr != nullptr) {if (mem_pos_map.find((uint64_t)ptr) != mem_pos_map.end()) {mem_size_map[dso_name] -= mem_pos_map[(uint64_t)ptr];fprintf(stderr, "dso_name %s in %p free size: %ld \n", dso_name, ptr, mem_pos_map[(uint64_t)ptr]);}}
}

然后我们重写了free函数,在函数内部我们调用glibc的free函数。下边判断是否是指定的so的内存,如果是就从mem_size_mapmem_pos_map中移除。

mem_size_map[dso_name]中存放的就是指定so的内存大小。

mem_pos_map来存储内存地址下的内存大小

以上则是简单的例子,我把它提交到了https://github.com/leap-ticking/dso_memory_stat位置,如果有需要可以直接使用,仅供参考。

ref

  • https://www.cnblogs.com/ho966/p/17671723.html
  • https://www.slideshare.net/slideshow/tips-of-malloc-free/16682403?from_search=1#4
  • https://github.com/google/tcmalloc
  • https://blog.binpang.me/2017/09/22/ptmalloc%E5%A0%86%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/
  • https://mp.weixin.qq.com/s/v-lXOFawW5iwZ24O_8f28w

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

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

相关文章

C语言的结构体类型

在我们使用C语言进行编写代码时&#xff0c;常常会使用已经给定的类型来创建变量&#xff0c;比如int型&#xff0c;char型&#xff0c;double型等&#xff0c;而当我们想创建一些较为复杂的东西时&#xff0c;单单用一个类型变量是没办法做到的&#xff0c;比如我们想创建一个…

shader 案例学习笔记之fract函数

fract函数 可以理解为模1取余&#xff0c;获取一个数的小数部分&#xff0c;如果参数是向量&#xff0c;那就是获取每个向量分量上的小数 案例一 #ifdef GL_ES precision mediump float; #endif// 渲染分辨率 uniform vec2 u_resolution; // 程序运行时间 uniform float u_ti…

如何利用 Smarter Balanced 塑造教育领域的 AI 治理

目录 定义挑战 以人为本的设计引领 融入多样性 探索以学生为中心的价值观 探索效果的层次和不同的影响 部位于加利福尼亚州的Smarter Balanced Assessment Consortium 是一个由会员主导的公共组织&#xff0c;为 K-12 和高等教育领域的教育工作者提供评估系统。该组织成立…

初学者指南:MyBatis 入门教程

主要介绍了Mybatis的基本使用、JDBC、数据库连接池、lombok注解&#xff01; 文章目录 前言 什么是Mybatis? 快速入门 使用Mybatis查询所有的用户信息 配置SQL提示 JDBC介绍 Mybatis 数据库连接池 lombok 总结 前言 主要介绍了Mybatis的基本使用、JDBC、数据库连接…

基于stm32单片机使用 RT-Thread 系统的 ADC 外设

一、ADC 介绍 来源&#xff1a;RT-Thread 文档中心   ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号&#xff0c;例如温度、压力、声音或者图像等&#xff0c;需要转换成更容易储存、处理和发射…

【Linux】:信号与信号产生

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家带来信号和信号的产生相关代码和知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到…

YOLOv10优改系列一:YOLOv10融合C2f_Ghost网络,让YoloV10实现性能的均衡

&#x1f4a5; &#x1f4a5;&#x1f4a5; &#x1f4a5;&#x1f4a5; &#x1f4a5;&#x1f4a5; &#x1f4a5;&#x1f4a5;神经网络专栏改进完整目录&#xff1a;点击 &#x1f497; 只需订阅一个专栏即可享用所有网络改进内容&#xff0c;每周定时更新 文章内容&#x…

微信自动通过好友和自动拉人进群,微加机器人这个功能太好用了

又发现一个好用的功能&#xff0c;之前就想找一个这种工具&#xff0c;现在发现可以利用微加机器人的两个功能来实现&#xff0c;分别是加好友和关键词拉群 首先 微加机器人的专业版 > 功能 > 加好友设置 可以设置一个关键词通过,这样别人加好友的时候只需要输入制定内…

实战案例(5)防火墙通过跨三层MAC识别功能控制三层核心下面的终端

如果网关是在核心设备上面&#xff0c;还能用MAC地址进行控制吗&#xff1f; 办公区域的网段都在三层上面&#xff0c;防火墙还能基于MAC来控制吗&#xff1f; 采用正常配置模式的步骤与思路 &#xff08;1&#xff09;配置思路与上面一样 &#xff08;2&#xff09;与上面区…

通信工程学习:什么是PDF策略决策功能

PDF策略决策功能 PDF策略决策功能&#xff08;Policy Decision Function, PDF&#xff09;在通信网络中&#xff0c;特别是在IP多媒体子系统&#xff08;IMS&#xff09;中&#xff0c;扮演着至关重要的角色。以下是对PDF策略决策功能的详细解释&#xff1a; 一、定义与功能概…

CentOS Stream 8中安装和使用 Docker

docker安装包-CSDN博客 〇、常用的docker命令 docker的作用&#xff1a; 快速进行软件的安装&#xff0c;便于软件环境的维护 docker的镜像: 压缩了指定软件的安装包的文件。使用镜像文件创建容器 docker的容器: 容器可以理解为就是一台小电脑。安装的linux系统&am…

【F的领地】项目拆解:小学教辅资料

项目介绍 虚拟资料项目的赛道其实一直可以做&#xff0c;实际上就是一个信息差项目。 知识付费是常青树&#xff0c;人天生有为知识付费的倾向。 而且虚拟资料通常一份 19~99 的资料有这方面需求很容易就能付款。 主要是产附属的流量再提问题。 我有个同乡&#xff0c;18年…

CircleProgressView 鸿蒙ArkTS自定义View实现圆形进度条

上篇的截图中除了一个上下的箭头&#xff0c;还有一个圆形进度条&#xff0c;今天我们来讲讲这个如何进行实现 我们看这个图形的构造&#xff0c;其实很简单&#xff1a;一个圆形图形&#xff0c;以及一个文本来显示进度 所以我们用一个层叠布局 绘制一个带颜色的圆形&#xff…

Java设计模式—面向对象设计原则(一) ----->开闭原则OCP(完整详解,附有代码+案例)

3.1开闭原则 对扩展开放&#xff0c;对修改关闭。在程序需要进行拓展的时候&#xff0c;不能去修改原有的代码&#xff0c;实现一个热插拔的效果。简言之&#xff0c;是为了使程序的扩展性好&#xff0c;易于维护和升级。想要达到这样的效果&#xff0c;我们需要使用接口和抽象…

怎么选择靠谱AI论文生成工具?看完我的试用都会明白!

2024年上半年开始AI论文写作工具开始火了&#xff0c;层出不穷&#xff01;作为一个经常需要写论文的懒人&#xff0c;我非常好奇这些AI工具的实际效果到底怎么样&#xff1f;为了测试不同工具的实力&#xff0c;我对他们都进行了试用&#xff0c;发现了一些意想不到的结果....…

路由器的固定ip地址是啥意思?固定ip地址有什么好处

‌在当今数字化时代&#xff0c;‌路由器作为连接互联网的重要设备&#xff0c;‌扮演着举足轻重的角色。‌其中&#xff0c;‌路由器的固定IP地址是一个常被提及但可能让人困惑的概念。‌下面跟着虎观代理小二一起将深入探讨路由器的固定IP地址的含义&#xff0c;‌揭示其背后…

QML入门之创建可重用的组件(一)

我们在日常开发中都会封装一些组件以便于项目内重复利用。QML创建可重用组件一般有两种方法。 自定义Item使用Component创建自定义组件 自定义Item 以一个自定义按钮举例&#xff1a; import QtQuick 2.12Rectangle {id: root// 自定义属性property string btnDis: qsTr(&qu…

51单片机+proteus仿真+基本实验学习1(跑马灯、独立按键和数码管)

目录 1.实验一跑马灯 1.1代码的生成 1.1.151单片机的延时函数的生成 1.1.251单片机的流水灯代码编写 1.2仿真框图 2.实验二I/O独立按键 2.1基本概念 2.1.1按键所需的基本知识 2.2代码的生成 2.2.1头文件定义的代码 2.2.2 执行代码 2.3仿真图 ​3实验三数码管 3.1基…

基于Verilog HDL的FPGA设计基础

第一章 Verilog数字集成电路设计方法概述 HDL(Hardware Description Language)----硬件描述语言 EDA(Electronic Design Automation)----电子设计自动化 VLSI(Very Large Scale Integrated)----超大规模集成电路 ASIC(Application Specific Integrated Circuit)----专用集成电路…

Unity射击游戏开发教程:(35)轰炸敌人

现在敌人和飞机已经慢慢地越来越有各自地地行为了,在本文中,我们将介绍如何创建一个具有以下行为的敌人: 飞机会来回弹跳。飞机将有 4 架无人机轰炸机围绕飞机旋转。无人机轰炸机会偶尔投下沿着屏幕传播的炸弹。如果炸弹击中玩家或在随机时间后就会爆炸。如果炸弹没有击中玩…