【c语言】malloc函数详解

news/2024/11/22 16:02:17/

谈到malloc函数相信学过c语言的人都很熟悉,但是malloc底层到底做了什么又有多少人知道。
1、关于malloc相关的几个函数
关于malloc我们进入Linux man一下就会得到如下结果:
这里写图片描述
也可以这样认为(window下)原型:

extern void *malloc(unsigned int num_bytes);

头文件:

#include<malloc.h>或者#include<alloc.h>两者的内容是完全一样的

如果分配成功:则返回指向被分配内存空间的指针
不然返回指针NULL
同时,当内存不再使用的时候,应使用free()函数将内存块释放掉。
关于:void*,表示未确定类型的指针,c,c++规定void*可以强转为任何其他类型的指针,关于void还有一种说法就是其他任何类型都可以直接赋值给它,无需进行强转,但是反过来不可以
malloc:
malloc分配的内存大小至少为参数所指定的字节数
malloc的返回值是一个指针,指向一段可用内存的起始位置,指向一段可用内存的起始地址,多次调用malloc所分配的地址不能有重叠部分,除非某次malloc所分配的地址被释放掉malloc应该尽快完成内存分配并返回(不能使用NP-hard的内存分配算法)实现malloc时应同时实现内存大小调整和内存释放函数(realloc和free)
malloc和free是配对的,如果申请后不释放就是内存泄露,如果无故释放那就是什么也没做,释放只能释放一次,如果一块空间释放两次或者两次以上会出现错误(但是释放空指针例外,释放空指针也等于什么也没做,所以释放多少次都是可以的。)
2、malloc和new
new返回指定类型的指针,并且可以自动计算所需要的大小。

int *p;
p = new int;//返回类型为int* ,分配的大小是sizeof(int)
p = new int[100];//返回类型是int*类型,分配的大小为sizeof(int)*100

而malloc需要我们自己计算字节数,并且返回的时候要强转成指定类型的指针。

int *p;
p = (int *)malloc(sizeof(int));

(1)malloc的返回是void*,如果我们写成了:p=malloc(sizeof(int));间接的说明了(将void转化给了int*,这不合理)
(2)malloc的实参是sizeof(int),用于指明一个整型数据需要的大小,如果我们写成p=(int*)malloc(1),那么可以看出:只是申请了一个一个字节大小的空间。
(3)malloc只管分配内存,并不能对其进行初始化,所以得到的一片新内存中,其值将是随机的。一般意义上:我们习惯性的将其初始化为NULL,当然也可以使用memset函数。
简单的说:
malloc函数其实就是在内存中找一片指定大小的空间,然后将这个空间的首地址给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址,这要看malloc函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上是连续的,而在物理上可以不连续。我们作为程序员,关注的是逻辑上的连续,其他的操作系统会帮着我们处理。
下面就来看看malloc具体是怎么实现的。
首先要了解操作系统相关的知识:
虚拟内存地址和物理内存地址
为了简单,现代操作系统在处理物理内存地址时,普遍采用虚拟内存地址技术。即在汇编程序层面,当涉及内存地址时,都是使用的虚拟内存地址。采用这种技术时,每个进程仿佛自己独享一片2N字节的内存,其中N是机器位数。例如在64位CPU和64位操作系统下每个进程的虚拟地址空间为264Byte。
这种虚拟地址空间的作用主要是简化程序的编写及方便操作系统对进程间内存的隔离管理,真实中的进程不太可能如此大的空间,实际能用到的空间大小取决于物理内存的大小。
由于在机器语言层面都是采用虚拟地址,当实际的机器码程序涉及到内存操作时,需要根据当前进程运行的实际上下文将虚拟地址转化为物理内存地址,才能实现对内存数据的操作。这个转换一般由一个叫MMU的硬件完成。
页与地址构成
在现代操作系统中,不论是虚拟内存还是物理内存,都不是以字节为单位进行管理的,而是以页为单位。一个内存页是一段固定大小的连续的连续内存地址的总称,具体到Linux中,典型的内存页大小为4096 Byte
所以内存地址可以分为页号和页内偏移量。下面以64位机器,4G物理内存,4K页大小为例,虚拟内存地址和物理内存地址的组成如下:
这里写图片描述
上面是虚拟内存地址,下面是物理内存地址。由于页大小都是4k,所以页内偏移都是用低12位表示,而剩下的高地址表示页号
MMU映射单位并不是字节,而是页,这个映射通过差一个常驻内存的数据结构页表来实现。现在计算机具体的内存地址映射比较复杂,为了加快速度会引入一系列缓存和优化,例如TLB等机制,下面给出一个经过简化的内存地址翻译示意图:
这里写图片描述
内存页与磁盘页
我们知道一般将内存看做磁盘的缓存,有时MMU在工作时,会发现页表表名某个内存页不在物理内存页不在物理内存中,此时会触发一个缺页异常,此时系统会到磁盘中相应的地方将磁盘页载入到内存中,然后重新执行由于缺页而失败的机器指令。关于这部分,因为可以看做对malloc实现是透明的,所以不再详述
真实地址翻译流程:
这里写图片描述
Linux进程级内存管理
2.2.1内存排布
明白了虚拟内存和物理内存的关系及相关的映射机制,下面看一下具体在一个进程内是如何排布内存的。
以Linux 64位系统为例。理论上,64bit内存地址空间为0x0000000000000000-0xFFFFFFFFFFFFFFF,这是个相当庞大的空间,Linux实际上只用了其中一小部分
具体分布如图所示:
这里写图片描述
对用户来说主要关心的是User Space。将User Space放大后,可以看到里面主要分成如下几段:
Code:这是整个用户空间的最低地址部分,存放的是指令(也就是程序所编译成的可执行机器码)
Data:这里存放的是初始化过的全局变量
BSS:这里存放的是未初始化的全局变量
Heap:堆,这是我们本文主要关注的地方,堆自底向上由低地址向高地址增长
Mapping Area:这里是与mmap系统调用相关的区域。大多数实际的malloc实现会考虑通过mmap分配较大块的内存空间,本文不考虑这种情况,这个区域由高地址像低地址增长
Stack:栈区域,自高地址像低地址增长
Heap内存模型:
一般来说,malloc所申请的内存主要从Heap区域分配,来看看Heap的结构是怎样的。
这里写图片描述
Linux维护一个break指针,这个指针执行堆空间的某个地址,从堆开始到break之间的地址空间为映射好的,可以供进程访问,而从break往上,是未映射的地址空间,如果访问这段空间则程序会报错
brk与sbrk
由上文知道,要增加一个进程实际上的可用堆大小,就需要将break指针向高地址移动。Linux通过brk和sbrk系统调用操作break指针。两个系统调用的原型如下:

int brk(void *addr);
void *sbrk(inptr_t increment);

brk将break指针直接设置为某个地址,而sbrk将break从当前位置移动increment所指定的增量。brk在执行成功时返回0,否则返回-1并设置为errno为ENOMEM,sbrk成功时返回break移动之前所指向的地址,否则返回(void*)-1;
资源限制和rlimirt
系统为每一个进程所分配的资源不是无限的,包括可映射的空间,因此每个进程有一个rlimit表示当前进程可用的资源上限,这个限制可以通过getrlimit系统调用得到,下面代码获取当前进程虚拟内存空间的rlimit
其中rlimt是一个结构体

struct rlimit
{rlimt_t rlim_cur;rlim_t rlim_max;
};

每种资源有硬限制和软限制,并且可以通过setrlimit对rlimit进行有条件限制作为软限制的上限,非特权进程只能设置软限制,且不能超过硬限制
实现malloc
(1)数据结构
首先我们要确定所采用的数据结构。一个简单可行方案是将堆内存空间以块的形式组织起来,每个块由meta区和数据区组成,meta区记录数据块的元信息(数据区大小、空闲标志位、指针等等),数据区是真实分配的内存区域,并且数据区的第一个字节地址即为malloc返回的地址
可以使用如下结构体定义一个block

typedef struct s_block *t_block;
struck s_block{size_t size;//数据区大小t_block next;//指向下个块的指针int free;//是否是空闲块int padding;//填充4字节,保证meta块长度为8的倍数char data[1];//这是一个虚拟字段,表示数据块的第一个字节,长度不应计入meta
};

(2)寻找合适的block
现在考虑如何在block链中查找合适的block。一般来说有两种查找算法:
First fit:从头开始,使用第一个数据区大小大于要求size的块所谓此次分配的块
Best fit:从头开始,遍历所有块,使用数据区大小大于size且差值最小的块作为此次分配的块
两种方式各有千秋,best fit有较高的内存使用率(payload较高),而first fit具有较高的运行效率。这里我们采用first fit算法

t_block find_block(t_block *last,size_t size){t_block b = first_block;while(b&&b->size>=size){*last = b;b = b->next;}return b;
}

find_block从first_block开始,查找第一个符合要求的block并返回block起始地址,如果找不到这返回NULL,这里在遍历时会更新一个叫last的指针,这个指针始终指向当前遍历的block.这是为了如果找不到合适的block而开辟新block使用的。

(3)开辟新的block
如果现有block都不能满足size的要求,则需要在链表最后开辟一个新的block。这里关键是如何只使用sbrk创建一个struct:

#define BLOCK_SIZE 24t_block extend_heap{t_block b;b = sbrk(0);if(sbrk(BLOCK_SIZE+s)==(void*)-1)return NULL;b->size = s;b->next - NULL;if(last)last->next = b;b->free = 0;return b;
};

(4)分裂block
First fit有一个比较致命的缺点,就是可能会让更小的size占据很大的一块block,此时,为了提高payload,应该在剩余数据区足够大的情况下,将其分裂为一个新的block

void split_block(t_block b,size_t s)
{t_block new;new = b->data;new->size = b->size-s-BLOCK_SIZE;new->next = b->next;new ->free = 1;b->size = s;b->next = new;
}

(5)malloc的实现
有了上面的代码,我们就可以实现一个简单的malloc.注意首先我们要定义个block链表的头first_block,初始化为NULL;另外,我们需要剩余空间至少有BLOCK_SIZE+8才执行分裂操作
由于我们需要malloc分配的数据区是按8字节对齐,所以size不为8的倍数时,我们需要将size调整为大于size的最小的8的倍数

size_t align8(size_t s)
{if(s&0x7 == 0)return s;return ((s>>3)+1)<<3;
}
#define BLOCK_SIZE 24
void *first_block=NULL;
void *mallloc(size_t size)
{t_block b,last;size_t s;//对齐地址s = align8(size);if(first_block)//查找适合blocklast = first_block;b = find_block(&last,s);if(b){//如果可以则分裂if((b->size-s)>=(BLOCK_SIZE + 8))split_block(b,s);b->free = 0;}else{//没有合适的block,开辟一个新的b=extend_heap(last,s);if(!b){return NULL;}else{b=extend_heap(NULL,s);if(!b){return NULL;}first_block = b;}}return b->data;
}

http://www.ppmy.cn/news/203424.html

相关文章

麦芒8能安装鸿蒙系统吗,首颗5G基带芯片麦芒8发布鸿蒙最新进展

5月29日&#xff0c;在台北国际电脑展上&#xff0c;联发科技发布突破性的全新5G移动平台&#xff0c;该款多模5G系统单芯片(SoC)采用7nm工艺制造&#xff0c;将为首批高端5G智能手机提供强劲动力&#xff0c;展示联发科技在5G方面的领先实力。 集成化的全新5G移动平台内置5G调…

华为麦芒 6 上手评测:能否继续麦芒 5 的辉煌?

华为麦芒 6 上手评测&#xff1a;能否继续麦芒 5 的辉煌&#xff1f; 从 2013 年的第一代麦芒 A199 到现在的麦芒 6&#xff0c;作为给年轻人专属定制的手机&#xff0c;麦芒系列已经走过了四个年头。华为麦芒 6 更是华为的第一款全面屏手机与四摄手机&#xff0c;而这也足以证…

麦芒6手机一键root,华为麦芒6怎么使用

麦芒6可以用什么软件root Root后对手机的影响、风险&#xff1a; 手机Root以后&#xff0c;系统可能无法达到最佳工作状态&#xff0c;部分对于系统安全性较高的功能可能无法正常运行&#xff08;比如手机银行&#xff09;。同时可能会存在兼容性不好、系统易受病毒入侵等问题。…

LeetCode中等题合集 python

目录 3. 无重复字符的最长子串53. 最大子数组和80. 删除有序数组中的重复项 II442. 数组中重复的数据209. 长度最小的子数组59. 螺旋矩阵 II24. 两两交换链表中的节点19. 删除链表的倒数第 N 个结点142. 环形链表 II 3. 无重复字符的最长子串 滑动窗口&#xff0c;类似的题目还…

2020年计算机专业最好的笔记本电脑,性价比最高的笔记本电脑推荐 2021年最值得入手的笔记本电脑...

买东西当然是买热门的品牌好&#xff0c;如果你想购置一台好的电脑&#xff0c;性价比是非常重要的&#xff0c;下面小编为大家带来今年热门的笔记本电脑排行&#xff0c;让你了解性价比最高的笔记本都有哪些。 第一名&#xff1a;HUAWEI MateBook X Pro 2020款 屏幕&#xff1…

【计算机专业入手好物】研0计算机专业电脑推荐与书籍等等

计算机专业好物推荐 电脑Y9000P为什么深度学习需要GPU&#xff1f; 游戏手柄阿修罗2Pro 书籍深度学习python编程从入门到实践 电脑 研究生确定录取之后&#xff0c;第一时间就开始选电脑。 本科期间我使用的电脑是intel芯片的Macbook pro。老师上课所使用的软件都是windows版本…

2022大一新生买什么电脑好?这几款值得考虑

​前两天&#xff0c;小葱给大家整理了一些新生买电脑的重要注意事项&#xff0c;看到很多家长朋友留言&#xff0c;希望小葱能给推荐几款适合孩子的笔记本电脑&#xff0c;这不就来了嘛&#xff01; 还是那句话&#xff0c;专业不同&#xff0c;需求不同&#xff0c;预算也不…

Windows电脑好用软件推荐

Windows电脑好用软件推荐 前言&#xff1a; 爱好折腾&#xff0c;喜欢捣鼓各种软件和工具&#xff0c;下面这些都是我使用过&#xff0c;还有些别人刚推荐的没有使用多久&#xff0c;但都觉得好用的简单小工具。 稍微花时间整理了一下&#xff0c;因为时间能力有限&#xff…