实现 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#和C 的历史与演变各有特色,未来前景也不同。1.C 由BjarneStroustrup在1983年发明,旨在将面向对象编程引入C语言,其演变历程包括多次标准化,如C 11引入auto关键字和lambda表达式,C 20引入概念和协程,未来将专注于性能和系统级编程。2.C#由微软在2000年发布,结合C 和Java的优点,其演变注重简洁性和生产力,如C#2.0引入泛型,C#5.0引入异步编程,未来将专注于开发者的生产力和云计算。

C 和XML的未来发展趋势分别为:1)C 将通过C 20和C 23标准引入模块、概念和协程等新特性,提升编程效率和安全性;2)XML将继续在数据交换和配置文件中占据重要地位,但会面临JSON和YAML的挑战,并朝着更简洁和易解析的方向发展,如XMLSchema1.1和XPath3.1的改进。

C 持续使用的理由包括其高性能、广泛应用和不断演进的特性。1)高效性能:通过直接操作内存和硬件,C 在系统编程和高性能计算中表现出色。2)广泛应用:在游戏开发、嵌入式系统等领域大放异彩。3)不断演进:自1983年发布以来,C 持续增加新特性,保持其竞争力。

C 多线程和并发编程的核心概念包括线程的创建与管理、同步与互斥、条件变量、线程池、异步编程、常见错误与调试技巧以及性能优化与最佳实践。1)创建线程使用std::thread类,示例展示了如何创建并等待线程完成。2)同步与互斥使用std::mutex和std::lock_guard保护共享资源,避免数据竞争。3)条件变量通过std::condition_variable实现线程间的通信和同步。4)线程池示例展示了如何使用ThreadPool类并行处理任务,提高效率。5)异步编程使用std::as

C#和C 的学习曲线和开发者体验有显着差异。 1)C#的学习曲线较平缓,适合快速开发和企业级应用。 2)C 的学习曲线较陡峭,适用于高性能和低级控制的场景。

C 通过第三方库(如TinyXML、Pugixml、Xerces-C )与XML交互。1)使用库解析XML文件,将其转换为C 可处理的数据结构。2)生成XML时,将C 数据结构转换为XML格式。3)在实际应用中,XML常用于配置文件和数据交换,提升开发效率。

C 学习者和开发者可以从StackOverflow、Reddit的r/cpp社区、Coursera和edX的课程、GitHub上的开源项目、专业咨询服务以及CppCon等会议中获得资源和支持。1.StackOverflow提供技术问题的解答;2.Reddit的r/cpp社区分享最新资讯;3.Coursera和edX提供正式的C 课程;4.GitHub上的开源项目如LLVM和Boost提升技能;5.专业咨询服务如JetBrains和Perforce提供技术支持;6.CppCon等会议有助于职业

现代C 设计模式利用C 11及以后的新特性实现,帮助构建更灵活、高效的软件。1)使用lambda表达式和std::function简化观察者模式。2)通过移动语义和完美转发优化性能。3)智能指针确保类型安全和资源管理。
