C语言实现free、malloc、new、delete

本文详细解析了C语言中的malloc、free函数以及new、delete运算符的工作原理,包括函数介绍、源码实现和内存分配策略。同时讨论了C++中的一些限制和堆管理的高级话题。

引言

在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函数的源码实现是从《程序员的自我修养》中抄的,然后看到后面说这个只是一个堆分配算法的一个描述,相当于只是一个思想的呈现,而一些堆的扩张、溢出机制、多线程机制还没有实现
<think>首先,用户的问题是:“我想理解在C/C++中动态内存管理时malloc/freenew/delete的区别及使用场景 请问C/C++中 malloc freenew delete 的使用区别及适用场景”。我需要基于提供的引用和系统指令来回答。 系统指令包括: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,如[^1]。 - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 参考的引用: - [^1]: 主要区别:new/delete调用构造函数/析构函数,malloc/free不调用;new自动计算大小,返回正确类型指针,malloc需要手动计算和类型转换。 - [^2]: new分配在自由存储区,可能是在堆上;malloc分配在堆上;new/delete可重载,malloc/free不可重载。 - [^3]: 对于内置类型,new不初始化;示例代码。 - [^4]: 表格总结区别:new/delete属于C++,调用构造/析构,返回对象指针,失败抛异常;malloc/free属于C,不调用构造/析构,返回void*,失败返回NULL。 我的回答需要结构化: 1. **介绍动态内存管理**:简要解释为什么需要动态内存管理。 2. **区别对比**:详细对比malloc/freenew/delete。 3. **使用场景**:讨论各自的适用场景。 4. **生成相关问题**:在末尾添加相关问题。 使用中文回答,并确保语法正确。 逐步构建回答: - **步骤1: 介绍**:动态内存管理允许在运行时分配和释放内存,用于处理大小未知的数据。 - **步骤2: 区别对比**: - **语法和功能**:malloc/free是C库函数;new/delete是C++运算符。 - **构造和析构**:new调用构造函数,delete调用析构函数;malloc/free只处理内存分配/释放。 - **类型安全**:new返回正确类型指针;malloc返回void*需要强制转换。 - **内存区域**:new分配在自由存储区(可能堆);malloc分配在堆。 - **失败处理**:new失败抛异常;malloc失败返回NULL。 - **可重载性**:new/delete可重载;malloc/free不可。 - **初始化**:new可以初始化对象;malloc只分配内存。 - **步骤3: 使用场景**: - 在C++中,优先使用new/delete,因为它支持面向对象特性。 - 在C中,只能使用malloc/free。 - 当需要与C代码交互时,可能使用malloc/free。 - 对于非对象类型(如原始内存),malloc/free可能更简单。 - **步骤4: 生成相关问题**:基于主题,添加3-4个相关问题。 在回答中,自然地添加引用标识。例如,当提到区别时,引用[^1]或[^4]。 确保行内数学表达式用$...$,但这里可能不需要数学表达式,除非涉及计算。用户问题没有数学内容,所以可能不需要。 独立公式:如果有公式,用$$...$$,但这里可能不需要。 最后,生成相关问题:例如: 1. new/delete如何实现构造和析构函数的调用? 2. 在C++中,如何重载newdelete运算符? 3. malloc/free在C++中有什么风险? 4. 动态内存管理的内存泄漏如何避免?</think>### C/C++中malloc/freenew/delete的区别及使用场景 在C/C++中,动态内存管理是程序运行时分配和释放内存的关键机制。`malloc/free`是C语言的标准库函数,而`new/delete`是C++引入的运算符。它们的主要区别在于类型安全、对象生命周期管理和内存区域等方面。以下我将逐步解释它们的区别,并讨论适用场景,确保回答基于可靠的技术细节[^1][^4]。 #### 1. **主要区别对比** - **语法和功能**: - `malloc/free`:是C标准库函数,`malloc`用于分配内存,`free`用于释放内存。它们只处理原始内存块,不涉及对象初始化或清理。例如: ```c int* p = (int*)malloc(sizeof(int)); // 手动计算大小并强制类型转换 free(p); // 仅释放内存 ``` `malloc`返回`void*`,需手动转换为目标类型[^1][^4]。 - `new/delete`:是C++运算符,`new`分配内存并调用构造函数初始化对象,`delete`释放内存并调用析构函数清理资源。例如: ```cpp int* p = new int; // 自动计算大小,返回int*指针 delete p; // 调用析构函数(如果适用) ``` 对于数组,需使用`new[]`和`delete[]`[^1][^3]。 - **对象生命周期管理**: - `new/delete`:在分配时调用构造函数(如类对象的初始化),在释放时调用析构函数(如释放成员资源)。这确保了对象的完整生命周期管理[^1][^4]。 - `malloc/free`:仅分配和释放原始内存,不调用任何构造函数或析构函数。如果用于对象,可能导致资源泄漏(如未释放内部指针)[^3][^4]。 - **类型安全和错误处理**: - `new`:返回目标类型的指针(如`int*`),类型安全;失败时抛出`std::bad_alloc`异常。 - `malloc`:返回`void*`,需强制类型转换,易出错;失败时返回`NULL`[^1][^4]。 - **内存区域**: - `malloc/free`:分配在操作系统的**堆(Heap)**上,是C语言的标准内存池。 - `new/delete`:分配在C++的**自由存储区(Free Store)**,可能位于堆上,但具体位置取决于编译器实现(可重载`new`运算符以改变位置)[^2][^4]。 - **可重载性**: - `new/delete`:可重载运算符,允许自定义内存分配策略(如使用静态存储区)。 - `malloc/free`:不可重载,是固定库函数[^2]。 #### 2. **适用场景** - **优先使用`new/delete`的场景**: - 在C++代码中操作对象(类或结构体),因为它自动处理构造和析构,避免内存泄漏。例如: ```cpp class MyClass { public: MyClass() { /* 构造函数 */ } ~MyClass() { /* 析构函数 */ } }; MyClass* obj = new MyClass(); // 调用构造函数 delete obj; // 调用析构函数 ``` - 需要类型安全和异常处理的场景,如大型应用开发[^1][^4]。 - 使用C++特性(如继承或多态)时,`delete`能正确调用派生类析构函数。 - **优先使用`malloc/free`的场景**: - 在纯C代码中,或与C库交互时(如调用C函数返回的内存指针)。 - 分配原始内存(如字节数组),不涉及对象初始化。例如: ```c char* buffer = (char*)malloc(1024); // 分配1KB缓冲区 free(buffer); ``` - 性能敏感场景,`malloc/free`开销略低(但差异通常微小)[^3][^4]。 - **混合使用的风险**: - 不可混用:`malloc`分配的内存不能用`delete`释放,反之亦然,否则导致未定义行为(如内存损坏)。C++中,应始终匹配使用:`new`配`delete`,`malloc`配`free`[^1][^3]。 #### 3. **总结建议** - 在C++中,**始终优先使用`new/delete`**,因为它提供完整的对象管理、类型安全和异常机制,符合面向对象原则[^1][^4]。 - 仅在遗留C代码或特定性能优化时使用`malloc/free`,并确保手动处理初始化和清理。 - 避免常见错误:如用`free`释放`new`分配的对象(忽略析构函数),或忘记数组语法(`delete[]` vs `delete`)[^3]。 通过理解这些区别,您可以更安全地管理动态内存,减少泄漏和错误。如果您有具体代码示例,我可以进一步分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lijiachang030718

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值