首页 > 其他分享 >malloc底层实现以及和new的比较

malloc底层实现以及和new的比较

时间:2024-10-21 14:32:01浏览次数:7  
标签:malloc free 内存 new 分配 空闲 底层

背景:

前几天去面试,被问到了一个问题:“malloc的底层实现是怎样的? 怎样防止内存碎片?” 当时答的不够好,现在再整理一下。

(本文档通过收集整理网上博客而来。先挖个坑,等有时间了去看一下《深入理解操作系统》的第九章虚拟内存,再重新整理一篇)

内存布局

Linux中每个进程都有自己的虚拟地址空间,通常会划分为几个区域。如下:

+--------------------------------------------------+
|           命令行参数和环境变量                      |
+--------------------------------------------------+
|           栈   局部变量,参数等。后进先出             |  (从高地址向低地址增长)
+--------------------------------------------------+
|           堆   动态内存分配 malloc calloc分配出来的  |  (从低地址向高地址增长)
+--------------------------------------------------+
|       BSS 段   未初始化的全局变量和静态变量           |
+--------------------------------------------------+
|   初始化数据段   已初始化的全局变量和静态变量           |
+--------------------------------------------------+
|       文本段    代码段,包含程序机器指令,只读         |
+--------------------------------------------------+

补充一点:

栈:速度快,不用程序员释放,空间小,容易栈溢出,有的系统只有8M。

堆:速度慢一点,空间大,要自己释放,管理起来难度大,容易内存泄漏。new 和 malloc都是操作的堆区。

 

malloc底层实现

小内存分配(通常小于128KB):

通常使用内存池(通过sbrk系统调用)来管理,先从内存池中查找一个空闲的块,将其标记为已分配,并返回指针。

DEFAULT_MMAP_THRESHOLD一般为128k,可通过mallopt进行设置。

sbrk通过移动堆指针来实现分配。

大内存分配:

通常使用mmap系统调用来直接分配内存。mmap将文件或匿名内存映射到进程的地址空间,返回一个指向映射区域的指针。

 Free List():

空闲列表(free list),malloc实现分配内存时会利用空闲链表来操作。

本质是一个链表,用于存储已经释放尚未分配的内存块,当需要分配时,优先从空闲链表中选择合适的内存卡。数据结构大致如下:

struct FreeBlock {
    size_t size;          // 块的大小
    bool bIsAvalib;  // 是否使用
    struct FreeBlock *next; // 指向下一个空闲块的指针
};

如下图: 来源于:https://www.jianshu.com/p/2fedeacfa797

 

大致流程:

初始化时:空闲列表通常为空。会向操作系统申请一块初始内存,作为第一个空闲块插入空闲列表中。

分配时:查找合适的空闲块,返回指针。(这里有不同的分配策略)

释放时:将释放的内存插入空闲列表中。并合并相邻的空闲块(为了避免出现内存碎片)。

分配策略:

大致有下列几种分配策略,配合内存管理模块,实际上会比较复杂,可能是几种方式组合使用。

1)从第一个空闲块开始找,找到空间够的。

2)从上一次分配的空闲块开始找个空间够的。

3)找个大小最接近的。

4)实际上可能会维护多个不同大小的空闲列表,根据malloc要申请的大小去使用不同的空闲列表。

 

关于free:

1) 调用free后,会自动合并相邻的空闲块,为避免出现内存碎片。

2)为什么free可以不用传大小?

因为每次malloc后,申请的内存会比实际使用的大一点,在用户使用的内存前面会存放内存块头部信息,里面会存着本内存块的大小。free时根据该大小来正确回收资源。

 如下图:是用vs2017测试的。

 

内存碎片:

定义: 

在频繁的内存分配和释放的过程中,可能会导致内存碎片。大致分两类:

内部碎片(Internal Fragmentation):已经被分配出去,结果无法使用的内存空间。比如用户申请了45字节,内存分配必须是4字节对齐的,系统可能会分配48个字节给用户。多出来的3个字节就成为了内部碎片。

外部碎片(External Fragmentation):由于频繁分配释放,导致内存中出现许多小的,不连续的内存区域。这些区域可能总和能满足新内存请求,但是由于不连续而无法使用。

影响:

性能下降:碎片过多,会导致再分配时需要更多的时间来查找合适的内存块。

内存利用率低:

系统稳定性问题:引发程序崩溃或系统挂起。

分配失败:用户无法分配足够的内存空间,影响用户程序运行。

解决策略:

1)内存池(Memory Pool):预先分配一块打内存,并划分为小块。每次申请时从内存池中获取。

2)紧凑(Compaction):移动内存中的数据块,将分散的空闲块合并。需要额外的开销。

3)使用不同的内存分配算法:

4)内存对齐和预取:确保内存分配是对齐的,减少内部碎片。

实际使用: 

操作系统提供了多种内存管理机制,Linux下如:slab分配器,伙伴系统等。 win下有Heap Manager等,都可以用于优化内存的分配释放。

 

new和delete

1.new和delete是运算符(malloc free是函数),理论上可以被重载,实现自己的new 运算符。

2.malloc失败了返回空指针,new失败了抛异常(没有被重载的情况下)。、

3.new 和 delete配套使用。  new数组 和 delete[]配套使用。

4.new不仅会分配内存,还会调用构造函数。

 

两个小开脑洞的问题:

问题1:malloc出来的指针,用delete释放?

问题2:new出来的指针,用free释放?

先上结论:

1)malloc出来的空间,可以通过delete回收。

2)new出来的空间,可以通过free回收。(如果new出来的是数组,无法用free回收,会引起崩溃)

3)上述理论可行,但是不建议这么做。还是要配套使用,或者使用智能指针管理变量。

上代码:

mallocTest.cpp

#include <iostream>

struct AAA
{
    AAA(){printf("AAA() ... \n");}
    ~AAA(){printf("~AAA() ... \n");}
    int index;
    float fdata;
    int arrData[1024 * 1024 * 100];
};

void mallocTest()
{
    printf("mallocTest  +++++ \n");
    int nnn = 2;
    auto pp = (AAA*)malloc(sizeof(AAA)*nnn);
    pp->index = 555;
    for(int i = 0; i < nnn; i++)
    {
        pp[i].index = i;
        int len = sizeof(pp[i].arrData) / sizeof(int);
        for(int j = 0; j < len; j++)
        {
            pp[i].arrData[j] = i * j *j;
        }
    }
    printf("mallocTest  new end. getchar free \n");
    getchar();
    delete pp;
    //free(pp);
    printf("mallocTest  free end. getchar quit \n");
    getchar();
}

void newTest()
{
    printf("newTest  +++++ \n");
    auto pp = new AAA();
    pp->index = 555;
    
        int len = sizeof(pp[0].arrData) / sizeof(int);
        for(int j = 0; j < len; j++)
        {
            pp[0].arrData[j] = 1 * j *j;
        }
    
    printf("newTest  new end. getchar free \n");
    getchar();
    free(pp);
    printf("newTest  free end. getchar quit \n");
    getchar();
}

int main()
{
    printf("main  +++++ \n");
    //newTest();
    mallocTest();
    return 0;
}

先执行mallocTest():

同时打开htop查看系统占用情况,按下回车,再次查看系统占用情况。

malloc分配完内存情况:

delete后内存情况:

 

再执行newtest:

new之后(free之前):

free之后:

 

标签:malloc,free,内存,new,分配,空闲,底层
From: https://www.cnblogs.com/xcywt/p/18475892

相关文章

  • Ubuntu系统中,使用matplotlib画图调用times new romain字体报错 findfont: Font family
    画图时报错,缺少字体findfont:Fontfamily['TimesNewRoman']notfound.FallingbacktoDejaVuSans.有两种解决方式:方式一:在线安装msttcorefonts包#安装msttcorefonts包这种方式需要ubuntu能连外网,否则因为访问source-forge失败而告终sudoaptupdatesudoapti......
  • C语言中的段错误(Segmentation Fault):底层原理及解决方法
    引言在C语言编程中,“段错误”(通常由操作系统信号SIGSEGV触发)是一种常见的异常情况,它表明程序试图访问不受保护的内存区域。本文将深入探讨段错误的原因、底层原理、常见情况以及如何调试和解决这类错误。段错误的定义段错误是一种运行时错误,通常由以下几种情况触发:访......
  • NewStarCTF-WP合集
    梦开始的地方第一~二周misc-decompress将所有压缩文件放在一个目录,使用Bandizip解压.001,然后使用md5计算器计算内部内容,即可获得flagmisc-用溯流仪见证伏特台首先进入所给链接找到威胁盟报告,发现由于b站原因导致视频不清晰,于是下载央视频后搜索该新闻,再读出信息powerj7km......
  • “JsonConvert”同时存在于“Newtonsoft.Json.Net20, Version=3.5.0.0, Culture=neutr
    原因是两个dll冲突了。需要去掉一个。Newtonsoft.Json(也称为Json.NET)是一个流行的开源JSON框架,用于.NET,它以其高性能、易用性和广泛的功能而闻名。它支持丰富的数据操作和序列化属性设置,如自定义转换器、日期时间格式控制、命名策略等。Json.NET还提供了序列化特性,如JsonObjectA......
  • 【高阶数据结构】揭开红黑树‘恶魔’的面具:深度解析底层逻辑
    高阶数据结构相关知识点可以通过点击以下链接进行学习一起加油!二叉搜索树AVL树大家好,我是店小二,欢迎来到本篇内容!今天我们将一起探索红黑树的工作原理及部分功能实现。红黑树的概念相对抽象,但只要我们一步步深入,定能慢慢揭开它的神秘面纱......
  • abc284D Happy New Year 2023
    给定整数N,已知N可以写成ppq的形式,其中p和q为不同质数,求p和q。1<=N<=9E18分析:p与q的最小值不超过3E6,可以枚举。#include<bits/stdc++.h>usingi64=longlong;std::vector<int>minp,prime;voidsieve(intn){ minp.assign(n+1,0); prime.clear(); for(inti=2......
  • C. New Game (二分)
    时隔多年又做题了这不得来水一篇博客题意:给出n个数,取一段连续的数字,最大数和最小数的差不超过k,使得取的数最多。解:对于每一个数,找到第最后一个连续的且与其差值不大于k的数,数一数期间一共有几个,然后取最大值。实现上先处理出连续的段,对于每一个数,找到对应的段,二分找出差值不大于......
  • 通过比较list与vector在简单模拟实现时的不同进一步理解STL的底层
     cplusplus.com/reference/list/list/?kw=list当我们大致阅读完list的cplusplus网站的文档时,我们会发现它提供的接口大致上与我们的vector相同。当然的,在常用接口的简单实现上它们也大体相同,但是它们的构造函数与迭代器的实现却大有不同。(食用本文时建议与文末的模拟实现代......
  • bolt.new本地化运行踩坑
    接入OpenAI端点安装依赖pnpmadd@ai-sdk/openaidiff--gita/app/lib/.server/llm/model.tsb/app/lib/.server/llm/model.tsindexf0d695c..5217697100644---a/app/lib/.server/llm/model.ts+++b/app/lib/.server/llm/model.ts@@-1,9+1,34@@import{createAnthro......
  • Admin整体底层实现原理
    Admin整体底层实现原理1.启动项目加载每个app目录下的admin.pyapp01/admin.pyadmin.site.register(models.Depart,admin.ModelAdmin)admin.site.register(models.UserInfo)1.1Admin文件的加载过程如何加载admin文件的呢?django/contrib/admin/apps具体代码如下:classAdm......