C的动态内存管理 free()和malloc()的简单实现——free()根据内存地址便知释放内存的空间大小(原理详解)

malloc与free

malloc 分配的内存是未初始化的,其中的字节内容是不确定的(可能是随机值)。
如果内存分配失败,malloc 返回一个空指针 NULL,可以通过检查返回值来判断是否分配成功

void* malloc (size_t size);

calloc 分配的内存会被初始化为全0。
calloc 在分配失败时会自动抛出错误(异常),可以使用异常处理机制来捕获和处理错误

void* calloc (size_t num, size_t size);

realloc 当malloc函数或者calloc函数申请的空间或者数组的空间不够大或太大时就可以用realloc函数对空间的大小进行调整。

void* realloc (void* ptr, size_t size);

 free 释放由malloc(),calloc(),realloc()函数动态开辟的内存空间,使其可以重新被分配。

void free (void* ptr);

malloc的底层

当开辟的空间小于128K时,调用 brk()函数,malloc的底层实现是系统调用函数brk(),其主要移动指针 _enddata(此时的 _enddata 指的是Linux地址空间中堆段的末尾地址,不是数据段的末尾地址)
当开辟的空间大于128K时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。

不同的内存分配器实现不一样,glibc的ptmalloc采用的是隐藏头风格,gemalloc采用其他的实现方式。

 malloc分配的内存为一个个chunk,以下是一个典型的 malloc_chunk 结构定义(以 glibc 为例)

struct malloc_chunk {size_t prev_size;          /* 前一个内存块的大小(如果合并的话) */size_t size;               /* 当前内存块的大小,包括边界标记 */struct malloc_chunk *fd;   /* 指向前一个空闲内存块的指针(用于空闲内存列表) */struct malloc_chunk *bk;   /* 指向下一个空闲内存块的指针(用于空闲内存列表) */
};

在进行malloc函数申请内存时,操作系统实际会申请大于malloc要求的长度

prev_size 字段表示前一个内存块的大小(如果当前内存块与前面的内存块合并在一起)。size 字段表示当前内存块的大小,包括边界标记。fdbk 字段分别表示指向前后空闲内存块的指针,用于维护空闲内存列表。

当释放一个内存块时,内存分配器可以检查 prev_size 字段以确定前一个内存块是否为空闲。如果前一个内存块为空闲,内存分配器可以将这两个相邻的空闲内存块合并成一个更大的空闲内存块。这样可以使内存分配更加高效,减少内存碎片。

malloc返回的指针不是指向了Header,而是指向了Payload开始处。

因此空间的大小记录在参数指针指向地址的前面,free的时候通过这个记录即可知道要释放的内存有多大。

总结:

(1)当调用malloc(size)时,它首先计算需要分配的内存块大小,包括用户请求的大小以及内存管理所需的额外空间(例如内存块的管理信息)。
(2)malloc 会遍历一个数据结构(例如空闲链表或空闲块列表),查找合适大小的空闲内存块。
(3)如果找到了合适的内存块,malloc 会将其标记为已分配,并返回一个指向该内存块的指针给用户。
(4)如果没有足够大的空闲内存块可用,malloc 可能需要扩展程序的虚拟内存空间。它通过系统调用(例如 brk 或 mmap)向操作系统请求更多的连续内存空间。
(5)当操作系统提供了更多的内存空间后,malloc 可以从新的空间中分配出合适大小的内存块,并将其标记为已分配。
(6)在内存块被释放时,通过调用 free 函数,malloc 将其标记为未分配,并将该内存块添加到空闲内存块的列表中,以便后续的内存分配可以重复使用它们。

 模拟实现

#include <unistd.h>   // 包含系统调用相关的头文件typedef struct Block {size_t size;       // 内存块的大小struct Block* next; // 指向下一个内存块的指针
} Block;Block* freeList = NULL;   // 空闲链表的头指针void* malloc(size_t size) {// 检查参数是否合法if (size <= 0) {return NULL;}// 计算需要分配的内存大小size_t blockSize = sizeof(Block) + size;// 在空闲链表中查找符合要求的内存块Block* prevBlock = NULL;Block* currBlock = freeList;while (currBlock != NULL) {if (currBlock->size >= blockSize) {// 找到合适大小的空闲块if (prevBlock != NULL) {// 删除这个空闲块prevBlock->next = currBlock->next;} else {// 这个空闲块是链表的头节点freeList = currBlock->next;}// 返回指向内存块的指针return (void*)(currBlock + 1);}prevBlock = currBlock;currBlock = currBlock->next;}// 没有找到可用的内存块,请求更多内存空间Block* newBlock = sbrk(blockSize);if (newBlock == (void*)-1) {return NULL;   // 请求失败,返回 NULL}// 返回指向新内存块的指针return (void*)(newBlock + 1);
}void free(void* ptr) {// 检查参数是否合法if (ptr == NULL) {return;}// 获取指向内存块起始位置的指针Block* block = ((Block*)ptr) - 1;// 将内存块标记为未分配状态,然后将其添加到空闲链表中block->next = freeList;freeList = block;
}

 结论:根据 malloc(size_t) 函数的调用,是有可能申请超过机器物理内存大小的内存块

在1G内存的计算机中是有可能malloc(1.2G)的

malloc能够申请的空间大小与物理内存的大小没有直接关系,仅与程序的虚拟地址空间相关。根据 malloc 函数的作用和原理,应用程序通过 malloc 函数在虚拟地址空间中申请内存,并且与物理内存没有直接的关系。

malloc 返回的是在虚拟地址空间中的地址,而物理内存的分配是由操作系统完成的。

假设需要申请的内存大小为 1.2GB,转换为字节为 2^30 × 1.2 Byte,这个数值仍然在 unsigned int 的表示范围内。

因为 malloc 函数需要一个 unsigned int 类型的参数来指定内存大小。

在当前使用的 Windows 环境中,可申请的最大内存空间通常超过 1.9GB。然而,具体可申请的内存大小受到操作系统版本、程序本身的大小、动态/共享库的使用情况、程序栈的大小等因素的影响。每次运行的结果可能存在差异,因为有些操作系统使用随机地址分布技术,导致进程的堆空间变小。

关键要点结论

(1) malloc分配的不是物理内存,是虚拟内存

现在的操作系统,内存管理通常是基于虚拟内存的,所以应用程序看到的内存地址(虚拟地址)与实际的物理内存地址(物理地址)是不同的。操作系统通过内存管理单元(MMU)来将虚拟地址转换为物理地址。

当应用程序首次访问这块内存时,操作系统发现对应的物理内存尚未分配,它会从可用的物理内存中分配相应的空间,并更新页表项以完成虚拟地址到物理地址的映射。如果这块内存从来没有被访问,那么就不会分配实际的物理内存,节约了内存。

(2) 调用free后,内存不会被操作系统立马回收

当使用free函数释放内存后,‌这块内存并不会立即被归还给操作系统。‌相反,‌这些被释放的内存首先会被内存管理器(‌如ptmalloc)‌保存起来,‌以便后续重用。‌这样做的主要目的是减少与操作系统的内存交互次数,‌从而降低系统调用的开销。

内存管理器会使用双链表等方式来管理这些被释放的内存块,‌当程序再次申请内存时,‌内存管理器会首先尝试从这些已释放的内存块中找到合适的块返回给程序,‌而不是直接向操作系统请求新的内存。‌这种机制有助于减少内存碎片和提高内存使用效率。

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

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

相关文章

Linux压缩和解压

目录 压缩和解压类 gzip/gunzip指令 zip/unzip指令 tar指令 压缩和解压类 gzip/gunzip指令 gzip用于压缩文件&#xff0c;gunzip用于解压缩文件。 解压后去掉了gz的后缀。 zip/unzip指令 ​​​​​​​ 将文件压缩后发给别人&#xff0c;别人再解压。 将整个文件压…

上千条备孕至育儿指南速查ACCESS\EXCEL数据库

虽然今天这个数据库的记录数才不过区区上千条&#xff0c;但是每条记录里的内容都包含四五个子标题&#xff0c;可以将相关的知识完整且整齐的展现&#xff0c;是个属于简而精的数据库。并且它包含2级分类。 【备孕】大类包含&#xff1a;备孕百科(19)、不孕不育(23)、精子卵子…

uniapp 微信小程序生成水印图片

效果 源码 <template><view style"overflow: hidden;"><camera device-position"back" flash"auto" class"camera"><cover-view class"text-white padding water-mark"><cover-view class"…

165万人在线《黑神话:悟空》登顶STEAM!勾起太多回忆,我冲啦!

本公众号由以下老铁赞助&#xff0c;感谢他们❗️ 2024年8月20日&#xff0c;一个平凡而又特殊的日子&#xff0c;国产游戏《黑神话&#xff1a;悟空》正式上线 Steam平台&#xff0c;在线人数突破165万&#xff0c;超越《反恐精英CS2》登顶热玩榜。 更牛逼的是 Steam 热玩排行…

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE啦&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好习惯&#…

Kafka快速入门:Kafka驱动JavaApi的使用

生产者和消费者是Kafka的核心概念之一&#xff0c;它们在客户端被创建和使用&#xff0c;并且包含了许多与Kafka性能和机制相关的配置。虽然Kafka提供的命令行工具能够执行许多基本操作&#xff0c;但它无法实现所有可能的性能优化。相比之下&#xff0c;使用Java API可以充分利…

打造更高效的项目:如何选择合适的管理工具

国内外主流的 10 款项目工程管理系统对比&#xff1a;PingCode、Worktile、Asana、Trello、Monday.com、ClickUp、Wrike、泛微项目协同工具、广联达项目管理软件、泛普OA。 在选择项目工程管理系统时&#xff0c;你是否经常感到无从下手&#xff0c;担心投资不当或工具不适合自…

Python 使用 matplotlib 显示图像

如果没有安装 matplotlib 需要先安装&#xff1a; pip install matplotlib一、读取图片并显示 import matplotlib.pyplot as pltimage_path "/Users/AlanWang4523/Desktop/Debug/files/image.png" image_array plt.imread(image_path)plt.figure("ImageShow…

[数据集][目标检测]停车场空位检测数据集VOC+YOLO格式7959张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;7959 标注数量(xml文件个数)&#xff1a;7959 标注数量(txt文件个数)&#xff1a;7959 标注…

【保姆级教程】5分钟上手 Coze 自建插件,把 AI 接入个人微信

上篇&#xff0c;给大家介绍了一款搭建微信机器人的开源项目&#xff1a; 搭建微信机器人的第4种方式&#xff0c;我造了一个摸鱼小助手 不同于需要付费的项目&#xff0c;它的定制化程度非常高~ 问题来了&#xff1a;怎么接入 AI 能力呢&#xff1f; 考虑到大家对 Coze 智能…

AI 智能体:从普通人到《黑神话:悟空》,保姆级教程让你瞬间变身!

大家好&#xff0c;我是木川 今天还没下班&#xff0c;就看到一款名为《黑神话:悟空》的游戏火爆全网&#xff0c;唤醒了无数玩家对大圣孙悟空的崇拜与向往。游戏中&#xff0c;悟空的七十二变让人叹为观止&#xff0c;但你是否想过&#xff0c;借助AI的力量&#xff0c;我们也…

实验十 编写子程序《汇编语言》- 王爽

一. 显示字符串 1. 需求 显示字符串是现实工作中经常要用到的功能&#xff0c;应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口&#xff0c;使用者可以决定显示的位置&#xff08;行、列&#xff09;、内容和颜色。 子程序描述 名称&#xff1a;show_str…

第六版页面

基本 明确定义 站点网关mqtt服务器 多个柜子使用的是主从模式 下发一个设备组其他的柜子跟着设置 具体的让后端进行详细管理 前端规范 字体规范 弹出框定义什么应该弹出什么不应该弹出 页面 主页 屏幕宽度有的没设置好 平面地图模式有的没重合好 日志改为告警在上面 日志…

cmake install setlocal错误

cmake中的代码如下&#xff1a; #设置安装目录的前缀 set(CMAKE_INSTALL_PREFIX $ENV{PUBLISH_DIR}) #这边的输出满足要求 message(STATUS "install dir:${CMAKE_INSTALL_PREFIX}") #指定安装 install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION bin …

机械学习—零基础学习日志(如何理解概率论3)

随机变量的函数分布 一维随机变量分布&#xff0c;可以看到下图&#xff0c;X为不同情况的概率。而x如果是大于等于X&#xff0c;那么当x在40以内时&#xff0c;没有概率&#xff0c;为0。 当x变大&#xff0c;在40-80之间&#xff0c;那么x大于X的概率为&#xff0c;0.7&…

Liunx搭建Rustdesk远程桌面服务

1、环境准备 Linux&#xff1a;centos7.9 rustdesk server安装包 很多新服务器并没有 wget 和unzip 可以通过yum自行安装下&#xff0c;如果系统中有wget但不能使用&#xff0c;直接卸载重装即可。 yum install wget wget --no-check-certificate https://github.com/rust…

《黑神话悟空》打不开解决方法介绍

黑神话悟空打不开怎么办&#xff1f;很多的玩家都非常的好奇自己的黑神话悟空为什么打不开&#xff0c;这里整理了黑神话悟空打不开解决方法介绍&#xff0c;详细的内容可以在这里进行了解&#xff0c;有需要的小伙伴们一起来看看吧&#xff01; 解决方法1&#xff1a;验证文件…

Go第一个程序

package mainimport "fmt"func main() {str : "hello go"fmt.Println(str) }上述很简单&#xff0c;如何使用os包获取命令行参数呢&#xff1f; package mainimport ("fmt""os" )func main() {fmt.Println(os.Args)str : "hello…

【Alibaba Cola 状态机】重点解析以及实践案例

【Alibaba Cola 状态机】重点解析以及实践案例 1. 状态模式 状态模式是一种行为型设计模式&#xff0c;允许对象在内部状态改变时改变其行为&#xff0c;简单地讲就是&#xff0c;一个拥有状态的context对象&#xff0c;在不同状态下&#xff0c;其行为会发生改变。看起来是改…

电脑开机LOGO修改教程_BIOS启动图片替换方法

准备工具&#xff1a;刷BIOS神器和change logo&#xff0c;打包下载地址&#xff1a;https://download.csdn.net/download/baiseled/89374686 一.打开刷BIOS神器&#xff0c;点击备份BIOS&#xff0c;保存到桌面 二.打开change logo&#xff0c;1.点击load image&#xff0c;选…