」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 實作 malloc() 和 free() — 首先重複使用舊內存

實作 malloc() 和 free() — 首先重複使用舊內存

發佈於2024-11-08
瀏覽:632

在本系列关于实现 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]刪除
最新教學 更多>
  • 如何在 JavaScript 中安全處理空值
    如何在 JavaScript 中安全處理空值
    JavaScript 中的空值檢查使用 JavaScript 時,正確處理「空」值至關重要。但是,標準空檢查可能並不總是按預期工作。讓我們探討原因並提供替代解決方案。 了解JavaScript 的Null Check在JavaScript 中,相等運算子(==) 和嚴格相等運算子(===)分別檢查值...
    程式設計 發佈於2024-11-08
  • 使用 AWS Lambda 為 Next.js 建置無伺服器後端
    使用 AWS Lambda 為 Next.js 建置無伺服器後端
    在不斷發展的 Web 開發世界中,利用無伺服器架構已經成為遊戲規則的改變者,尤其是對於 Next.js 應用程式而言。透過整合 AWS Lambda,開發人員可以建立可擴展且高效的後端,而無需管理伺服器的開銷。在這篇文章中,我們將探討如何使用 AWS Lambda 為您的 Next.js 應用程式...
    程式設計 發佈於2024-11-08
  • 當你開始學習程式語言時會發生什麼
    當你開始學習程式語言時會發生什麼
    在數位時代,學習程式語言不僅是一種優勢,而且是一種必要。無論您的目標是提升職業生涯、建立創新應用程序,還是只是更好地了解數位世界,程式設計技能都是不可或缺的。讓我們深入探討您應該踏上這趟變革之旅的原因和方法。 學習程式語言的重要性 職涯發展 根據美國勞工統計局的數據...
    程式設計 發佈於2024-11-08
  • 如何使用匿名結構或聯合編譯 C 程式碼?
    如何使用匿名結構或聯合編譯 C 程式碼?
    使用匿名結構/聯合編譯C 代碼出現了關於如何使用匿名結構或聯合編譯C 代碼的問題,如C 具有使用聯合的匿名欄位。在 C 中,嘗試使用包含匿名聯合的命名結構建立類似的結構會導致編譯錯誤。 錯誤訊息表示匿名聯合和結構欄位未在結構聲明中聲明。若要在 C 中啟用此功能,必須使用 -fms-extension...
    程式設計 發佈於2024-11-08
  • 如何使用 OpenSSL 和 C++ 產生 SHA256 雜湊值?
    如何使用 OpenSSL 和 C++ 產生 SHA256 雜湊值?
    使用 OpenSSL 和 C 產生 SHA256 雜湊 雜湊是一種加密技術,用於產生資料的唯一指紋或摘要。對於 SHA256(安全雜湊演算法 2,256 位元),此摘要是 256 位元十六進位字串。 SHA256 通常用於檢查資料完整性、驗證數位簽章和安全儲存密碼。 在本文中,我們將介紹如何使用 O...
    程式設計 發佈於2024-11-08
  • 探索軟體工程師的就業市場
    探索軟體工程師的就業市場
    Introduction In this article, we dive into the process of extracting and analyzing job data from LinkedIn, leveraging a combination of Python...
    程式設計 發佈於2024-11-08
  • 如何優化FastAPI中大數據的JSON回應效能?
    如何優化FastAPI中大數據的JSON回應效能?
    利用大數據提高 FastAPI 中 JSON 回應的效能FastAPI 用戶在透過端點傳回大量 JSON 資料時遇到嚴重延遲。全面的解決方案涉及解決多個因素,包括資料檢索、序列化和客戶端顯示。 資料提取和讀取如範例程式碼中突出顯示的,資料最初使用Pandas 的read_parquet() 函數從P...
    程式設計 發佈於2024-11-08
  • 在 React 中的選項卡之間發送資料。
    在 React 中的選項卡之間發送資料。
    本文將介紹如何在 React 全域元件之間發送數據,甚至在不同的瀏覽器標籤中也是如此。 故事 想像您有一個項目列表,例如用戶。 每個使用者都可以在模態視窗中開啟進行修改。 您沒有任何後端訂閱,這表示如果使用者發生變化,使用者清單不會自動與後端同步。 因此,一旦使用者的個人資料...
    程式設計 發佈於2024-11-08
  • 如何從 WPF 中的非調度程序執行緒修改 ObservableCollection?
    如何從 WPF 中的非調度程序執行緒修改 ObservableCollection?
    「這種類型的CollectionView 不支援從與調度程式執行緒不同的執行緒更改其SourceCollection」問題描述A DataGrid 綁定非同步填充的ObservableCollection 會拋出錯誤,指出不允許從非Dispatcher 執行緒對SourceCollection 進行...
    程式設計 發佈於2024-11-08
  • SQL Server 中的日期時間和時間戳記有什麼不同?
    SQL Server 中的日期時間和時間戳記有什麼不同?
    了解SQL Server 中日期時間和時間戳記之間的差異雖然SQL Server 中的日期時間和時間戳記資料型別都處理日期和時間,但它們表現出根本的區別。 Datetime 是專為儲存日期和時間資訊而設計的資料類型。它支援多種格式和日期/時間計算。另一方面,Timestamp 並不是用來儲存日期和時...
    程式設計 發佈於2024-11-08
  • 如何使用現代前端開發技術建立令人驚嘆的網站
    如何使用現代前端開發技術建立令人驚嘆的網站
    在当今的数字时代,网页设计在给访问者留下持久印象方面发挥着至关重要的作用。随着数以百万计的网站争夺注意力,创建一个令人惊叹的、脱颖而出的网站比以往任何时候都更加重要。现代前端开发技术彻底改变了网站的构建方式,使设计美观、实用且响应灵敏的网站变得更加容易,从而提供最佳的用户体验。本文将深入探讨可帮助您...
    程式設計 發佈於2024-11-08
  • 讓我們建立一個簡單的 React hook 來偵測瀏覽器及其功能
    讓我們建立一個簡單的 React hook 來偵測瀏覽器及其功能
    使用者代理嗅探是最受歡迎的瀏覽器偵測方法。不幸的是,由於多種原因,前端開發不太容易使用它。瀏覽器供應商不斷嘗試讓嗅探變得不可能。因此,每個瀏覽器都有自己的使用者代理字串格式,解析起來非常複雜。 有一個更簡單的方法可以使用瀏覽器 CSS API 實現相同的目的,我將向您展示。那麼讓我們建立瀏覽器功能...
    程式設計 發佈於2024-11-08
  • 使用 Golang 的電子商務平台:了解乾淨的架構
    使用 Golang 的電子商務平台:了解乾淨的架構
    了解乾淨的架構 清潔架構(Clean Architecture)由 Robert C. Martin 推廣,是一種軟體設計理念,它將設計元素劃分為環級別。乾淨架構的主要規則是程式碼依賴關係只能從外層向內移動。這意味著: 業務規則不依賴 UI 或資料庫。 業務規則對外界一無所知。 ...
    程式設計 發佈於2024-11-08
  • TypeScript 與 JavaScript:開發人員的主要區別
    TypeScript 與 JavaScript:開發人員的主要區別
    JavaScript 是網路的核心語言,而 TypeScript 是基於它的現代增強語言。兩者都很強大,但它們的用途略有不同。這是一個快速細分: 1. 模式安全 JavaScript:鬆散型別。變數可以動態更改類型,從而導致潛在的運行時錯誤。 TypeScript:靜態型別。您定義...
    程式設計 發佈於2024-11-08
  • 每個 PHP 專家都該回答的問題
    每個 PHP 專家都該回答的問題
    自 1990 年代中期以來,PHP 一直是 Web 開發的重要語言,廣泛應用於網站後端。儘管出現了新的語言和框架,PHP 仍然很重要,尤其是在 WordPress 等平台上。如果您能解決以下八個主題,那麼您對PHP 的理解就相當高級了。 1. 建構開發環境 部署 PHP 開發環境...
    程式設計 發佈於2024-11-08

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3