引言
在C语言中,这个malloc和free函数可谓是非常重要的一个函数,它们是用来从堆区中申请内存资源和释放内存资源的,如果这两个函数你能够掌握的话,说明你的编程能力已经达到了一个高度了,已经能够甩掉很大一部分人了。并且写项目会用到非常多这两个函数,并且C++中的new和delete运算符其实就是调用了这两个函数,而且如果能够清楚这两个函数的底层实现,那么自己的项目能力和对内核的理解能力也会有很大的提升。
头文件
#include <stdlib.h> //C风格
#include <cstdlib> //CPP风格
一、free函数
1.函数介绍
功能:从堆区中释放以
p
t
r
ptr
ptr 开始的堆区空间
不知道堆区和栈区等的内存分配问题,可以看我之前的博客内存分配
void free(void* ptr);
2.源码实现
typedef struct _heap_header
{
enum {
HEAP_BLOCK_FREE = 0xABABABAB, // magic number of free block
HEAP_BLOCK_USED = 0xCDCDCDCD, // magic number of used block
}type; // block type FREE/USED
unsigned size; // block size including header
struct _heap_header* next;
struct _heap_header* prev;
} heap_header;
#define ADDR_ADD(a,o) (((char*)(a)) + o)
#define HEADER_SIZE (sizeof(heap_header))
static heap_header* list_head = NULL;
void free(void* ptr)
{
heap_header* header = (heap_header*)ADDR_ADD(ptr, -HEADER_SIZE);
if (header->type != heap_header::HEAP_BLOCK_USED)
return;
header->type = heap_header::HEAP_BLOCK_FREE;
if (header->prev != NULL && header->prev->type == heap_header::HEAP_BLOCK_FREE) {
//merge
header->prev->next = header->next;
if (header->next != NULL)
header->next->prev = header->prev;
header->prev->size += header->size;
header = header->prev;
}
if (header->next != NULL && header->next->type == heap_header::HEAP_BLOCK_FREE) {
//merge
header->size += header->next->size;
header->next = header->next->next;
}
}
3.源码解析
-
struct _heap_header:
1.首先定义了一个枚举类型,里面有两个值, m a g i c n u m b e r magic\ number magic number 音译为“魔数”,就是代码中直接出现的数字,为避免阅读和维护该模块不方便,所以直接用单词拼写的方式,一眼就知道是什么意思,顾名思义就是通过给其赋上具体的数来判断该区域是否已经分配或者空闲,一般这种值大概率是很难随机生成的。2.里面的变量还有 s i z e size size 分配的大小, p r e v prev prev 和 n e x t next next ,这个是把整个堆区已经分配了的区域用一个双链表串起来,在全局静态变量中可以明显看到这个链表头。基本上长如下图这个样子:

-
两个#define:
第一个中的 a a a 和 o o o 相当于两个参数,用的时候会替换,意为强转为 c h a r ∗ char* char∗ 类型并且偏移 o o o 个字节。第二个就是简单的替换为这个结构体的大小了,一般工程类都这样写。 -
前两行:
1.可以看出首先把地址向前位移了整个结构体大小,这就说明这个内存分布是可用的区域前面紧挨着的就是这个区域的“头部”,储存着这个区域的类型、大小等信息。2.移动后判断当前区域的类型是否是分配过的,如果还没 U S E D USED USED 那也用不着释放了,直接 r e t u r n return return出去。 -
中间的if:
先把类型赋值为 F R E E FREE FREE ,然后看上一个区域是否是已经空闲状态,如果是那就合并,这个操作就是双链表的删除某个结点做法,这些个区域应该是连续的吧,不然怎么可能在链表上直接删除就算合并了。 -
最后的if:
还是判断下一个结点是否为空闲的,不然就合并,这个操作也是双链表的删除结点的做法。
二、malloc函数
1.函数介绍
功能:从堆区中申请
s
i
z
e
size
size 大小的字节数,返回地址最靠前的指针
void* malloc(unsigned size);
2.源码实现
void* malloc(unsigned size)
{
heap_header* header;
if (size == 0) // 1
return NULL;
header = list_head;
while (header != 0) { // 2
if (header->type == heap_header::HEAP_BLOCK_USED) { // 3
header = header->next;
continue;
}
if (header->size > size + HEADER_SIZE && // 4
header->size <= size + HEADER_SIZE * 2) {
header->type = heap_header::HEAP_BLOCK_USED;
}
if (header->size > size + HEADER_SIZE * 2) { // 5
// split
heap_header* next = (heap_header*)ADDR_ADD(header, size + HEADER_SIZE);
next->prev = header;
next->next = header->next;
next->type = heap_header::HEAP_BLOCK_FREE;
next->size = header->size - (size - HEADER_SIZE);
header->next = next;
header->size = size + HEADER_SIZE;
header->type = heap_header::HEAP_BLOCK_USED;
return ADDR_ADD(header, HEADER_SIZE);
}
header = header->next;
}
return NULL;
}
3.源码解析
- 1.首先如果申请的大小为 0 0 0 那就直接返回
- 2.否则定义一个指针指向链表头,然后循环找到合适的区域,直至到末端,因为
#define NULL 0,所以 N U L L NULL NULL 和 0 0 0 是一回事 - 3.假如当前区域的类型被标记为已经用过了,那么继续看下一个结点
- 4.假如当前真个块中的区域大于申请的大小加上头部刚好大,且已经不能够容纳分裂成一个头部时,就标记为占用,并且返回给用户,这里没有 r e t u r n return return 应该是有某种机制导致可以不用 r e t u r n return return也可以返回,暂且放着。
- 5.假如当前的块中区域的大小大于所申请的大小,那就分裂,其实就是双链表的插入操作,然后分裂出去的标记为空闲状态,然后给用户地址。
三、new、delete
这两个运算符其实就是调用 m a l l o c malloc malloc 和 f r e e free free 函数,直接给出代码:
extern "C" void* malloc(unsigned int);
extern "C" void free(void*);
void* operator new(unsigned int size)
{
return malloc(size);
}
void operator delete(void* p)
{
free(p);
}
void* new[](unsigned int size)
{
return malloc(size);
}
void operator delete[](void* p)
{
free(p);
}
四、测试
main.cpp
#include <cstdio>
#include <iostream>
using namespace std;
typedef struct _heap_header
{
enum {
HEAP_BLOCK_FREE = 0xABABABAB, // magic number of free block
HEAP_BLOCK_USED = 0xCDCDCDCD, // magic number of used block
}type; // block type FREE/USED
unsigned size; // block size including header
struct _heap_header* next;
struct _heap_header* prev;
} heap_header;
#define ADDR_ADD(a,o) (((char*)(a)) + o)
#define HEADER_SIZE (sizeof(heap_header))
static heap_header* list_head = NULL;
void free(void* ptr)
{
heap_header* header = (heap_header*)ADDR_ADD(ptr, -HEADER_SIZE);
if (header->type != heap_header::HEAP_BLOCK_USED)
return;
header->type = heap_header::HEAP_BLOCK_FREE;
if (header->prev != NULL && header->prev->type == heap_header::HEAP_BLOCK_FREE) {
//merge
header->prev->next = header->next;
if (header->next != NULL)
header->next->prev = header->prev;
header->prev->size += header->size;
header = header->prev;
}
if (header->next != NULL && header->next->type == heap_header::HEAP_BLOCK_FREE) {
//merge
header->size += header->next->size;
header->next = header->next->next;
}
}
void* malloc(unsigned size)
{
heap_header* header;
if (size == 0)
return NULL;
header = list_head;
while (header != 0) {
if (header->type == heap_header::HEAP_BLOCK_USED) {
header = header->next;
continue;
}
if (header->size > size + HEADER_SIZE &&
header->size <= size + HEADER_SIZE * 2) {
header->type = heap_header::HEAP_BLOCK_USED;
return ADDR_ADD(header, HEADER_SIZE);
}
if (header->size > size + HEADER_SIZE * 2) {
// split
heap_header* next = (heap_header*)ADDR_ADD(header, size + HEADER_SIZE);
next->prev = header;
next->next = header->next;
next->type = heap_header::HEAP_BLOCK_FREE;
next->size = header->size - (size - HEADER_SIZE);
header->next = next;
header->size = size + HEADER_SIZE;
header->type = heap_header::HEAP_BLOCK_USED;
return ADDR_ADD(header, HEADER_SIZE);
}
header = header->next;
}
return NULL;
}
int main()
{
int* p = (int*)malloc(sizeof(int));
*p = 10;
cout << *p << endl;
free(p);
int *q = (int*)malloc(10 * sizeof(int));
for(int i = 0; i < 10; ++i)
{
*q = i;
cout << *q << endl;
q++;
}
free(q);
return 0;
}
测试结果:

五、遇到的问题
在C++中,MON不能访问到,只能通过_heap::MON来访问,而C语言中可以可以,应该是c++中引入了命名空间的缘故
#include <stdio.h>
struct _heap
{
enum {MON = 1} type;
};
int main()
{
int a = MON;
//cout << a << endl;
return 0;
}
六、总结
- 我摊牌了,这个malloc和free函数的源码实现是从《程序员的自我修养》中抄的,然后看到后面说这个只是一个堆分配算法的一个描述,相当于只是一个思想的呈现,而一些堆的扩张、溢出机制、多线程机制还没有实现
本文详细解析了C语言中的malloc、free函数以及new、delete运算符的工作原理,包括函数介绍、源码实现和内存分配策略。同时讨论了C++中的一些限制和堆管理的高级话题。
2238

被折叠的 条评论
为什么被折叠?



