”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 实现 malloc() 和 free() — 首先重用旧内存

实现 malloc() 和 free() — 首先重用旧内存

发布于2024-11-08
浏览:473

在本系列关于实现 malloc() 和 free() 的上一篇文章中,我们展示了如何通过释放新块来重用内存块并减少堆。然而,当前的函数引入了一个微妙的问题:它优先考虑重用较新的块,这可能会导致内存消耗随着时间的推移而增加。为什么会发生这种情况?让我们来分解一下。

通过重用最近的块来减少堆

考虑以下场景。首先,我们分配四个内存块:


void *ptr1 = abmalloc(8); 无效 *ptr2 = abmalloc(8); 无效 *ptr3 = abmalloc(8); 无效 *ptr4 = abmalloc(8);
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
内存结构可以这样可视化:

Implementing malloc() and free() — old memory reused first

现在,我们发布第一块和第三块……


abfree(ptr1); abfree(ptr3);
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
...产生以下结构:

Implementing malloc() and free() — old memory reused first

然后我们分配另一个相同大小的块:


void *ptr5 = abmalloc(8);
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
当函数 abmalloc() 开始搜索最近的空闲块时,它会重用顶部的块。如果我们现在释放最后一个块:

Implementing malloc() and free() — old memory reused first

如果我们现在释放最后一个区块……


abfree(ptr4);
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
...我们可以将堆大小减少一个 8 字节块,因为前一个块不再空闲:

Implementing malloc() and free() — old memory reused first

旧区块的再利用

现在,想象一下相同的场景,但进行了一项修改:我们的函数开始从最旧的块开始搜索空闲块。初始结构将是相同的......

Implementing malloc() and free() — old memory reused first

...我们再次释放第一和第三个内存块:

Implementing malloc() and free() — old memory reused first

这次,第一个块将被重用:

Implementing malloc() and free() — old memory reused first

现在,当我们释放最后一个块时,顶部将有两个空闲块,使我们能够将堆减少两个 8 字节块:

Implementing malloc() and free() — old memory reused first

这个例子说明了,通过优先选择较新的块,我们最终会积累旧的未使用的块,浪费内存并导致不必要的堆增长。解决方案是修改搜索策略,优先重用旧块。

实施对旧区块的偏好

为了解决这个问题,我们首先在标头中添加一个指向下一个块的指针。我们还将创建一个指向第一个块的全局指针,这样我们就可以从它开始搜索:


typedef 结构头 { 结构头*上一个,*下一个; size_t 尺寸; 可用布尔值; 标题; 标头*第一个= NULL; 标头*最后= NULL;
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
我们将在两种不同的情况下创建带有标头的内存块,所以让我们进行一个小的重构:我们将这个逻辑提取到一个分配和初始化标头的辅助函数(包括将字段 next 设置为 NULL):


标题 *header_new(标题 *前一个, size_t 大小, bool 可用) { 标头 *header = sbrk(sizeof(标头) 大小); 标题->上一个=上一个; 标题->下一个= NULL; 标题->大小=大小; 标头->可用= false; 返回标头; }
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
通过这个新函数,我们可以简化 abmalloc() 中的逻辑:


void *abmalloc(size_t 大小) { 如果(大小== 0){ 返回空值; } 标题*标题=最后; while (标题!= NULL) { if (标题->可用 && (标题->大小 >= 大小)) { 标头->可用= false; 返回标头1; } 标题=标题->上一页; } 最后 = header_new(最后, 大小, false); 返回最后1个; }
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
现在我们可以访问第一个和最后一个块,并且给定一个块,我们可以找出前一个和下一个块。我们还知道,当指向第一个块的指针为空时,还没有分配任何块。所以在这种情况下,我们将立即分配块,并初始化第一个和最后一个:


void *abmalloc(size_t 大小) { 如果(大小== 0){ 返回空值; } 如果(第一个== NULL){ 第一个 = 最后一个 = header_new(NULL, 大小, false); 返回第一个1; }
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
如果firstit不再为NULL,则已经分配了块,因此我们将开始搜索可重用的块。我们将继续使用变量 header 作为迭代器,但搜索将从最旧的块开始,而不是从最近的块开始:


标头 *标头 = 第一个;
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
在每次迭代时,我们将前进到序列中的下一个块,而不是向后到上一个块:


while (标题!= NULL) { if (标题->可用 && (标题->大小 >= 大小)) { 标头->可用= false; 返回标头1; } 标题=标题->下一个; }
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
逻辑保持不变:如果我们找到足够大小的可用块,则将其返回。否则,如果遍历列表后没有找到可重用的块,则分配一个新块:


最后 = header_new(最后, 大小, false);
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
现在,我们需要调整最后一个块(分配后,倒数第二个)。它指向 NULL,但现在它应该指向新块。为此,我们将前一个块的下一个字段设置为新的最后一个块:


最后一个->上一个->下一个 = 最后一个; 返回最后1个; }
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
abfree() 中的调整

函数 abfree() 基本上保持相同的结构,但现在我们必须处理一些边缘情况。当我们释放堆顶部的块时,一个新块将成为最后一个块,正如我们在此代码段中所做的那样:


最后 = 标题->上一个; brk(标头)
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
这里,指针头引用堆栈上最后一个可用的非空块。我们有两种可能的情况:

    当前块有一个前一个块,它将成为新的最后一个块。在这种情况下,我们应该将该块的next指针设置为NULL。
  1. 当前块没有前一个块(即,它是第一个也是最旧的块)。当它被释放时,堆栈是空的。在这种情况下,我们不会尝试更新不存在的块的字段,而是先将其设置为 NULL,表示不再有分配的块。
我们的实现方式如下:


最后 = 标题->上一个; 如果(最后!= NULL){ 上一个->下一个= NULL; } 别的 { 首先=空; } brk(标头);
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
结论

我们的函数 abmalloc() 和 abfree() 现在看起来像这样:


typedef 结构头 { 结构头*上一个,*下一个; size_t 尺寸; 可用布尔值; 标题; 标头*第一个= NULL; 标头*最后= NULL; 标头 *header_new(标头 *前一个, size_t 大小, bool 可用) { 标头 *header = sbrk(sizeof(标头) 大小); 标题->上一个=上一个; 标题->下一个= NULL; 标题->大小=大小; 标头->可用= false; 返回标头; } 无效* abmalloc(size_t大小){ 如果(大小== 0){ 返回空值; } 如果(第一个== NULL){ 第一个 = 最后一个 = header_new(NULL, 大小, false); 返回第一个1; } 标题*标题=第一个; while (标题!= NULL) { if (标题->可用 && (标题->大小 >= 大小)) { 标头->可用= false; 返回标头1; } 标题=标题->下一个; } 最后 = header_new(最后, 大小, false); 最后一个->上一个->下一个=最后一个; 返回最后1个; } 无效 abfree(无效 *ptr) { 如果(ptr == NULL){ 返回; } 标头 *标头 = (标头*) ptr - 1; 如果(标题==最后){ while ((标题->上一个!= NULL) && 标题->上一个->可用) { 标题=标题->上一页; } 最后=标题->上一个; 如果(最后!= NULL){ 上一个->下一个= NULL; } 别的 { 首先=空; } brk(标头); } 别的 { 标头->可用= true; } }
void *ptr1 = abmalloc(8);
void *ptr2 = abmalloc(8);
void *ptr3 = abmalloc(8);
void *ptr4 = abmalloc(8);
此更改使我们能够节省更多内存。然而,仍有一些问题需要解决。例如,考虑以下场景:我们请求分配 8 字节的内存块,而 abmalloc() 重用了一个 1024 字节的内存块。显然存在浪费。

我们将在下一篇文章中看到如何解决这个问题。

版本声明 本文转载于:https://dev.to/adambrandizzi/implementing-malloc-and-free-old-memory-reused-first-3585如有侵犯,请联系[email protected]删除
最新教程 更多>
  • `console.log`显示修改后对象值异常的原因
    `console.log`显示修改后对象值异常的原因
    foo = [{id:1},{id:2},{id:3},{id:4},{id:id:5},],]; console.log('foo1',foo,foo.length); foo.splice(2,1); console.log('foo2', foo, foo....
    编程 发布于2025-07-10
  • 如何避免Go语言切片时的内存泄漏?
    如何避免Go语言切片时的内存泄漏?
    ,a [j:] ...虽然通常有效,但如果使用指针,可能会导致内存泄漏。这是因为原始的备份阵列保持完整,这意味着新切片外部指针引用的任何对象仍然可能占据内存。 copy(a [i:] 对于k,n:= len(a)-j i,len(a); k
    编程 发布于2025-07-10
  • 为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    为什么使用固定定位时,为什么具有100%网格板柱的网格超越身体?
    网格超过身体,用100%grid-template-columns 为什么在grid-template-colms中具有100%的显示器,当位置设置为设置的位置时,grid-template-colly修复了?问题: 考虑以下CSS和html: class =“ snippet-code”> g...
    编程 发布于2025-07-10
  • PHP与C++函数重载处理的区别
    PHP与C++函数重载处理的区别
    作为经验丰富的C开发人员脱离谜题,您可能会遇到功能超载的概念。这个概念虽然在C中普遍,但在PHP中构成了独特的挑战。让我们深入研究PHP功能过载的复杂性,并探索其提供的可能性。在PHP中理解php的方法在PHP中,函数超载的概念(如C等语言)不存在。函数签名仅由其名称定义,而与他们的参数列表无关。...
    编程 发布于2025-07-10
  • 如何在Java中正确显示“ DD/MM/YYYY HH:MM:SS.SS”格式的当前日期和时间?
    如何在Java中正确显示“ DD/MM/YYYY HH:MM:SS.SS”格式的当前日期和时间?
    如何在“ dd/mm/yyyy hh:mm:mm:ss.ss”格式“ gormat 解决方案: args)抛出异常{ 日历cal = calendar.getInstance(); SimpleDateFormat SDF =新的SimpleDateFormat(“...
    编程 发布于2025-07-09
  • 如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    为有效的slug生成首先,该函数用指定的分隔符替换所有非字母或数字字符。此步骤可确保slug遵守URL惯例。随后,它采用ICONV函数将文本简化为us-ascii兼容格式,从而允许更广泛的字符集合兼容性。接下来,该函数使用正则表达式删除了不需要的字符,例如特殊字符和空格。此步骤可确保slug仅包含...
    编程 发布于2025-07-09
  • 如何使用Depimal.parse()中的指数表示法中的数字?
    如何使用Depimal.parse()中的指数表示法中的数字?
    在尝试使用Decimal.parse(“ 1.2345e-02”中的指数符号表示法表示的字符串时,您可能会遇到错误。这是因为默认解析方法无法识别指数符号。 成功解析这样的字符串,您需要明确指定它代表浮点数。您可以使用numbersTyles.Float样式进行此操作,如下所示:[&& && && ...
    编程 发布于2025-07-09
  • 在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8 MySQL表中正确将Latin1字符转换为UTF8的方法
    在UTF8表中将latin1字符转换为utf8 ,您遇到了一个问题,其中含义的字符(例如,“jáuòiñe”)在utf8 table tabled tablesset中被extect(例如,“致电。为了解决此问题,您正在尝试使用“ mb_convert_encoding”和“ iconv”转换受...
    编程 发布于2025-07-09
  • 用户本地时间格式及时区偏移显示指南
    用户本地时间格式及时区偏移显示指南
    在用户的语言环境格式中显示日期/时间,并使用时间偏移在向最终用户展示日期和时间时,以其localzone and格式显示它们至关重要。这确保了不同地理位置的清晰度和无缝用户体验。以下是使用JavaScript实现此目的的方法。方法:推荐方法是处理客户端的Javascript中的日期/时间格式化和时...
    编程 发布于2025-07-09
  • 为什么我会收到MySQL错误#1089:错误的前缀密钥?
    为什么我会收到MySQL错误#1089:错误的前缀密钥?
    mySQL错误#1089:错误的前缀键错误descript [#1089-不正确的前缀键在尝试在表中创建一个prefix键时会出现。前缀键旨在索引字符串列的特定前缀长度长度,可以更快地搜索这些前缀。了解prefix keys `这将在整个Movie_ID列上创建标准主键。主密钥对于唯一识别...
    编程 发布于2025-07-09
  • 如何使用FormData()处理多个文件上传?
    如何使用FormData()处理多个文件上传?
    )处理多个文件输入时,通常需要处理多个文件上传时,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    编程 发布于2025-07-09
  • 如何简化PHP中的JSON解析以获取多维阵列?
    如何简化PHP中的JSON解析以获取多维阵列?
    php 试图在PHP中解析JSON数据的JSON可能具有挑战性,尤其是在处理多维数组时。要简化过程,建议将JSON作为数组而不是对象解析。执行此操作,将JSON_DECODE函数与第二个参数设置为true:[&&&&& && &&&&& json = JSON = JSON_DECODE($ j...
    编程 发布于2025-07-09
  • 解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    Resolving Incorrect String Value Exception When Inserting EmojiWhen attempting to insert a string containing emoji characters into a MySQL database us...
    编程 发布于2025-07-09
  • 如何使用Python的请求和假用户代理绕过网站块?
    如何使用Python的请求和假用户代理绕过网站块?
    如何使用Python的请求模拟浏览器行为,以及伪造的用户代理提供了一个用户 - 代理标头一个有效方法是提供有效的用户式header,以提供有效的用户 - 设置,该标题可以通过browser和Acterner Systems the equestersystermery和操作系统。通过模仿像Chro...
    编程 发布于2025-07-09

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3