C中动态内存分配器的自定义实现

Posted

技术标签:

【中文标题】C中动态内存分配器的自定义实现【英文标题】:Custom implementation of dynamic memory allocators in C 【发布时间】:2021-07-24 22:13:50 【问题描述】:

所以对于我的 C 任务,我需要实现一个动态内存分配器,它具有与标准库(如 malloc、free、realloc)类似的接口。我将分配器实现为可以由其他程序调用的函数库。虚拟堆将由一个简单的伙伴分配算法管理。

我给出的功能是:

void * virtual_sbrk(int32_t increment);
pretty much the same as the real-world sbrk and brk syscalls. I don't need to implement this.

void init_allocator(void * heapstart, uint8_t initial_size, uint8_t min_size);
This function will be called once at the beginning and initialise the virtual heap.

void * virtual_malloc(void * heapstart, uint32_t size);
mallocs memory

int virtual_free(void * heapstart, void * ptr);
frees memory

void * virtual_realloc(void * heapstart, void * ptr, uint32_t size);
reallocates memory

void virtual_info(void * heapstart);
prints the current state of the buddy allocator to standard output.

这是我目前的问题: 你如何初始化堆并在没有任何东西的情况下实现 malloc?就像我不能使用 malloc 或任何预先存在的分配器函数一样。到目前为止,我已经尝试使用带有包含内存作为值的节点的链表。例如,如果初始大小为 3,最小大小为 1,我将有 5 个节点,根节点包含 8 个字节,另外两个节点每个包含 4 个字节,最后还有 2 个节点,每个节点包含 2 个字节。但是我仍然对如何使用 sbrk 或堆的结构感到困惑。我浏览了在线资源,但仍然对如何构建堆内存感到困惑。

以下是我目前的代码:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

struct node
    size_t memory;
    struct node *nextInLine;
;

void printNode(const struct node *nd, const char *comment)

    if(nd == NULL)

        printf("%s is null\n", comment);
    
    else

        printf("%s: memory:%d address:%p nextInLine:%p\n",
            comment,
            nd->memory,
            nd,
            nd->nextInLine);
    




void printList(const struct node *list)
    printf("Printing List:\n");
    const struct node *t;
    t = list;
    if(t == NULL)
        printf("current node is empty\n");
    
    else
        while(t)
            printNode(t, "node");
            t = t->nextInLine;
        
    



void * virtual_sbrk(int32_t increment) 
    void *p = malloc(increment);
    return p;



uint8_t return_init_size(uint8_t size)
    return size;


struct node *getNewNode(const uint8_t memory_size)

    struct node *newNode = NULL;
    double two = 2;
    size_t m_size = memory_size;
    double result = pow(two, m_size);
    newNode = virtual_sbrk(result);
    if(newNode != NULL)
        newNode->memory = result;
        newNode->nextInLine = NULL;
     
    else
        printf("Allocation error: newNode is still NULL\n");
    
    return newNode;



void init_allocator(void * heapstart, uint8_t initial_size, uint8_t min_size) 

    //error catchers
    if(initial_size == 0)
        printf("Initial size is 0\n");
    
    if(initial_size < min_size)
        printf("initial_size is smaller than min_size\n");
    


    //initialising the virtual heap using a linked array with nodes the memory size of 2^some_size 
    uint8_t i = initial_size;
    struct node *first = heapstart;
    heapstart = first;
    struct node *tail = NULL;
    while(i >= min_size)
        if(first == NULL)
            first = getNewNode(i);
            if(first != NULL)
                tail = first;
            
        
        else
            tail->nextInLine = getNewNode(i);
            if(tail->nextInLine != NULL)
                tail = tail->nextInLine;
            
            tail->nextInLine = getNewNode(i);
            if(tail->nextInLine != NULL)
                tail = tail->nextInLine;
            
        
        i -= 1;
    

    printList(first);



void * virtual_malloc(void * heapstart, uint32_t size) 
   
    if(size == 0)
        return NULL;
    

    

    return NULL;


int virtual_free(void * heapstart, void * ptr) 

    return 1;


void * virtual_realloc(void * heapstart, void * ptr, uint32_t size) 

    return NULL;


void virtual_info(void * heapstart) 
    

如果有人可以帮助解释我将如何执行此操作,那就太好了,就像我需要遵循的结构一样,如果这有意义的话。

【问题讨论】:

您可以使用免费列表。这实际上是 malloc 的工作原理,请在此处查看更多信息 how-do-free-and-malloc-work 你可以使用一个大的全局数组,即'char pool[1000000];' 【参考方案1】:

您可以像 glibc malloc 一样使用 sbrkmmap

glibc malloc 与线程一起工作,使用称为 arenas 的东西。

malloc 初始化时会调用sbrk 来扩展映射内存。

当发生大分配或创建新线程时,malloc 最终会调用 mmap

mmap 在进程的地址空间中分配一个新的映射。 sbrk 扩展当前映射以使其更大。

sbrk 的简单示例:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>

#define HEAP_SZ 0x8000

int main(void) 
    void *p = sbrk(0);
    printf("current break addr = %p\n", p);
    sbrk(HEAP_SZ);
    void *n = sbrk(0);
    printf("new break addr = %p\n", n);
    return 0;

第一次调用(带参数 0)返回当前程序中断。 当指定大于 0 的大小时,程序中断被扩展,因此在下次使用参数 0 调用时,将返回新的程序中断。

你可以这样做:


unsigned long heap_mem_sz = 0;
void *heap_start_addr = NULL;

void init_heap(void) 
    void *p = sbrk(0);
    #if DEBUG
        printf("current break addr = %p\n", p);
    #endif
    sbrk(HEAP_SZ);
    heap_mem_sz = (unsigned long)HEAP_SZ;
    void *n = sbrk(0);
    #if DEBUG
        printf("new break addr = %p\n", n);
    #endif
    heap_start_addr = (void *)n;
    return;

拥有关于全局变量的信息可以让您继续开发分配器实现。

您可以在第一次请求分配时致电init_heap()

现在您可以返回该分配并制作“顶部块”。

它将是一个结构与其他块相同的块,但包含分配从中获取内存的所有内存,并且在分配时它会缩小。

此外,一旦堆内存已满,您将需要执行一些操作,因此请考虑再次调用 mmapsbrk 之类的系统调用。

malloc 上的链表用于 bin。它们用于搜索可以满足新分配的已释放块,以便您重用不再使用的块。

对于这样的链表,你可以创建一个全局的:

struct heap_chunk *freed_chain = NULL

请求内存时,首先检查freed_chain是否为NULL,如果不是,则遍历链表,直到找到与用户请求兼容的块,或者下一个指针为NULL。

如果这些块中的任何一个是有效的,您将需要从链表中取消该块的链接,并使前一个块指向下一个块,因此不再有内存请求访问它,因为它现在已分配且未释放.

在释放内存时,您需要将一个新块链接到该链表。

显然在 malloc 上,出于优化目的,这更复杂,并且存在一些具有不同大小要求和不同属性的不同 bin 以加快分配速度。

【讨论】:

以上是关于C中动态内存分配器的自定义实现的主要内容,如果未能解决你的问题,请参考以下文章

C语言动态内存

UNIX再学习 -- 死磕内存管理

C语言学习笔记--动态内存分配

2016 - 2 - 16 动态内存分配与静态内存分配

C之动态内存分配(三十四)

动态内存分配