实现 malloc() 和 free() — 分割大块
在本系列的上一篇文章中,我们看到了选择要重用的内存块的顺序如何导致更大或更少的内存消耗,并且我们更改了函数来避免这种情况浪费。但我们需要解决另一个甚至更严重的问题:有时,一个非常大的内存块可能会占用几个较小块可以使用的空间。考虑下面的情况,我们分配一大块内存,释放它,然后分配两个小得多的块:
void *ptr1 = abmalloc(128); void *ptr2 = abmalloc(8); abfree(ptr1); void *ptr3 = abmalloc(8); void *ptr4 = abmalloc(8);
这里,我们有一个空闲的 128 字节内存块,当我们分配一个只有 8 字节的块时,所有 128 字节都变得不可用。当我们分配另一个 8 字节块时,堆需要再次增长。这不是对内存的有效利用。
对于这种情况至少有两种流行的解决方案。一种更有效的方法是使用 bins:列出按大小分组的块。这是一种更复杂、更有效的方法,但也更复杂。另一种更简单的选择是找到一个大块并将其分成更小的块。我们将遵循这种方法。
但请记住:更简单并不意味着简单;-)
初始重构
在开始之前,让我们进行一个小的重构。目前, header_new() 函数做了两件事:为新块分配更多内存并初始化其标头,设置元数据和指向前一个块的指针。初始化标头的部分可能有用,所以让我们将其提取出来。我们将创建两个新函数来提高可读性:
- header_plug() 函数,它将初始化的块“插入”到前一个和下一个块。
- header_init() 函数,用于设置块元数据的初始值(大小和可用性)。
它们的外观如下:
void header_init(Header *header, size_t size, bool available) { header->size = size; header->available = available; } void header_plug(Header *header, Header *previous, Header *next) { header->previous = previous; if (previous != NULL) { previous->next = header; } header->next = next; if (next != NULL) { next->previous = header; } }
现在,我们只需要修改 header_new() 即可使用这些新函数:
Header *header_new(Header *previous, size_t size, bool available) { Header *header = sbrk(sizeof(Header) + size); header_init(header, size, available); header_plug(header, previous, NULL); return header; }
(此外,我们可以从 abmalloc() 函数中删除最后一行 ->previous->next = last; 行,因为 header_plug() 现在可以处理该问题。)
分裂方块
有了这些工具,让我们创建 header_split() 函数。给定标头和所需的最小大小,如果原始块足够大以包含
,则此函数会将内存块分成两部分- 所需尺寸,
- 新区块的新标头,以及
- 一点额外的内存。
首先,我们检查块是否足够大:
Header *header_split(Header *header, size_t size) { size_t original_size = header->size; if (original_size >= size + sizeof(Header)) {
如果满足这个条件,我们就分割区块。首先,我们通过减去标头的大小和 abmalloc 请求的空间来减小当前块的大小:
void *ptr1 = abmalloc(128); void *ptr2 = abmalloc(8); abfree(ptr1); void *ptr3 = abmalloc(8); void *ptr4 = abmalloc(8);
这会在当前块之后留下一个内存空间,我们将用它来创建新块。我们计算这个新块的指针:
void header_init(Header *header, size_t size, bool available) { header->size = size; header->available = available; } void header_plug(Header *header, Header *previous, Header *next) { header->previous = previous; if (previous != NULL) { previous->next = header; } header->next = next; if (next != NULL) { next->previous = header; } }
现在我们有了指向新块的指针,我们用 header_init() 初始化它的头:
Header *header_new(Header *previous, size_t size, bool available) { Header *header = sbrk(sizeof(Header) + size); header_init(header, size, available); header_plug(header, previous, NULL); return header; }
我们使用 header_plug() 将新块连接到前一个和下一个块:
Header *header_split(Header *header, size_t size) { size_t original_size = header->size; if (original_size >= size + sizeof(Header)) {
如果原始块是最后一个,那么新块现在将是最后一个,因此我们更新最后一个指针:
header->size = original_size - size - sizeof(Header);
最后,我们返回新块:
Header *new_header = header + sizeof(Header) + header->size;
如果原始块不够大,我们只需返回原始块:
header_init(new_header, size, true);
更新 abmalloc()
现在,我们只需要回到 abmalloc() 函数,在找到可用块的地方,我们调用 header_split() 来尝试拆分它:
header_plug(new_header, header, header->next);
如果区块可以分裂,则返回新区块。否则,原始块将像以前一样保留并返回。
关于块分裂的注意事项
请注意,我们在原始块的末尾创建了新块。我们可以在一开始就创建它,但是通过在最后创建新的已用块,新的空闲块会更接近旧块。这样,下次调用 abmalloc() 时就会首先找到它。
分割大内存块是向前迈出的一步,但存在相反的问题:小内存块可能会导致碎片,发出更大的请求会导致堆增长。我们将在下一篇文章中看到如何解决这个问题。
以上是实现 malloc() 和 free() — 分割大块的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

C语言数据结构:树和图的数据表示与操作树是一个层次结构的数据结构由节点组成,每个节点包含一个数据元素和指向其子节点的指针二叉树是一种特殊类型的树,其中每个节点最多有两个子节点数据表示structTreeNode{intdata;structTreeNode*left;structTreeNode*right;};操作创建树遍历树(先序、中序、后序)搜索树插入节点删除节点图是一个集合的数据结构,其中的元素是顶点,它们通过边连接在一起边可以是带权或无权的数据表示邻

文件操作难题的真相:文件打开失败:权限不足、路径错误、文件被占用。数据写入失败:缓冲区已满、文件不可写、磁盘空间不足。其他常见问题:文件遍历缓慢、文本文件编码不正确、二进制文件读取错误。

C语言函数是代码模块化和程序搭建的基础。它们由声明(函数头)和定义(函数体)组成。C语言默认使用值传递参数,但也可使用地址传递修改外部变量。函数可以有返回值或无返回值,返回值类型必须与声明一致。函数命名应清晰易懂,使用驼峰或下划线命名法。遵循单一职责原则,保持函数简洁性,以提高可维护性和可读性。

C语言函数名定义包括:返回值类型、函数名、参数列表和函数体。函数名应清晰、简洁、统一风格,避免与关键字冲突。函数名具有作用域,可在声明后使用。函数指针允许将函数作为参数传递或赋值。常见错误包括命名冲突、参数类型不匹配和未声明的函数。性能优化重点在函数设计和实现上,而清晰、易读的代码至关重要。

C35 的计算本质上是组合数学,代表从 5 个元素中选择 3 个的组合数,其计算公式为 C53 = 5! / (3! * 2!),可通过循环避免直接计算阶乘以提高效率和避免溢出。另外,理解组合的本质和掌握高效的计算方法对于解决概率统计、密码学、算法设计等领域的许多问题至关重要。

C语言函数是可重复利用的代码块,它接收输入,执行操作,返回结果,可将代码模块化提高可复用性,降低复杂度。函数内部机制包含参数传递、函数执行、返回值,整个过程涉及优化如函数内联。编写好的函数遵循单一职责原则、参数数量少、命名规范、错误处理。指针与函数结合能实现更强大的功能,如修改外部变量值。函数指针将函数作为参数传递或存储地址,用于实现动态调用函数。理解函数特性和技巧是编写高效、可维护、易理解的C语言程序的关键。

算法是解决问题的指令集,其执行速度和内存占用各不相同。编程中,许多算法都基于数据搜索和排序。本文将介绍几种数据检索和排序算法。线性搜索假设有一个数组[20,500,10,5,100,1,50],需要查找数字50。线性搜索算法会逐个检查数组中的每个元素,直到找到目标值或遍历完整个数组。算法流程图如下:线性搜索的伪代码如下:检查每个元素:如果找到目标值:返回true返回falseC语言实现:#include#includeintmain(void){i

C语言多线程编程指南:创建线程:使用pthread_create()函数,指定线程ID、属性和线程函数。线程同步:通过互斥锁、信号量和条件变量防止数据竞争。实战案例:使用多线程计算斐波那契数,将任务分配给多个线程并同步结果。疑难解答:解决程序崩溃、线程停止响应和性能瓶颈等问题。
