malloc 实现?

Posted

技术标签:

【中文标题】malloc 实现?【英文标题】:malloc implementation? 【发布时间】:2011-07-22 06:41:59 【问题描述】:

我正在尝试为 C 实现 mallocfree,但我不确定如何重用内存。我目前有一个struct,看起来像这样:

typedef struct _mem_dictionary 
    void *addr;
    size_t size;
    int freed;
 mem_dictionary;

我的malloc 看起来像这样:

void *malloc(size_t size) 
    void *return_ptr = sbrk(size);
    if (dictionary == NULL) 
        dictionary = sbrk(1024 * sizeof(mem_dictionary));

    dictionary[dictionary_ct].addr = return_ptr;
    dictionary[dictionary_ct].size = size;
    dictionary[dictionary_ct].freed = 1;
    dictionary_ct++;

    return return_ptr;

当我释放内存时,我会将地址标记为0(这表明它是空闲的)。在我的malloc 中,我将使用for 循环在数组中查找任何等于0 的值,然后将内存分配给该地址。我有点困惑如何实现这一点。

【问题讨论】:

这里有一篇关于 dlmalloc 的精彩文章:g.oswego.edu/dl/html/malloc.html 【参考方案1】:

最简单的方法是保留一个空闲块的链表。在malloc 中,如果列表不为空,则搜索一个足够大的块来满足请求并返回它。如果列表为空或找不到这样的块,则调用sbrk 从操作系统分配一些内存。在free 中,您只需将内存块添加到空闲块列表中。作为奖励,您可以尝试合并连续释放的块,并且您可以更改选择要返回的块的策略(首先适合,最佳适合,...)。如果块大于请求,也可以选择拆分块。

一些示例实现(未经测试,显然不是线程安全的,使用风险自负):

typedef struct free_block 
    size_t size;
    struct free_block* next;
 free_block;

static free_block free_block_list_head =  0, 0 ;
static const size_t overhead = sizeof(size_t);
static const size_t align_to = 16;

void* malloc(size_t size) 
    size = (size + sizeof(size_t) + (align_to - 1)) & ~ (align_to - 1);
    free_block* block = free_block_list_head.next;
    free_block** head = &(free_block_list_head.next);
    while (block != 0) 
        if (block->size >= size) 
            *head = block->next;
            return ((char*)block) + sizeof(size_t);
        
        head = &(block->next);
        block = block->next;
    

    block = (free_block*)sbrk(size);
    block->size = size;

    return ((char*)block) + sizeof(size_t);


void free(void* ptr) 
    free_block* block = (free_block*)(((char*)ptr) - sizeof(size_t));
    block->next = free_block_list_head.next;
    free_block_list_head.next = block;

注意:(n + align_to - 1) & ~ (align_to - 1) 是将n 舍入为大于n 的最接近的align_to 倍数的技巧。这仅适用于 align_to 是 2 的幂并且取决于数字的二进制表示。

align_to 是2 的幂时,它只有一个位集,因此align_to - 1 具有所有最低位集(即align_to 的形式为000...010...0 , 而align_to - 1 的形式为000...001...1)。这意味着~ (align_to - 1) 已设置所有高位,而未设置低位(即它的形式为111...110...0)。所以x & ~ (align_to - 1) 会将x 的所有低位设置为零,并将其向下舍入到最接近的align_to 倍数。

最后,将align_to - 1 添加到size 确保我们四舍五入到最接近的align_to 倍数(除非size 已经是align_to 的倍数,在这种情况下我们想要得到size )。

【讨论】:

malloc函数第一行的神奇作用是什么? 它将(size + sizeof(size_t)) 舍入为大于(size + sizeof(size_t)) 的最接近的align_to 倍数。这仅适用于 align_to 是 2 的幂。 使用链表缓存来保持分配的内存(而不是重新分配)以加速图形程序(在 malloc 上花费太多时间)的类似技术被用作Jon Bentley 所著的 Programming Pearls 一书中第 9 栏:代码调优的第一部分中的示例。遗憾的是,这本书的示例中没有包含代码,所以看到这样的代码对我来说特别有用。【参考方案2】:

您不想将字典条目的size 字段设置为零——您将需要该信息以供重复使用。相反,只有在块被释放时才设置freed=1

您无法合并相邻的块,因为可能有对 sbrk() 的干预调用,所以这使得这更容易。你只需要一个 for 循环来搜索一个足够大的释放块:

typedef struct _mem_dictionary

    void *addr;
    size_t size;
    int freed;
 mem_dictionary;


void *malloc(size_t size)

     void *return_ptr = NULL;
     int i;

     if (dictionary == NULL) 
         dictionary = sbrk(1024 * sizeof(mem_dictionary));
         memset(dictionary, 0, 1024 * sizeof(mem_dictionary));
     

     for (i = 0; i < dictionary_ct; i++)
         if (dictionary[i].size >= size
          && dictionary[i].freed)
     
         dictionary[i].freed = 0;
         return dictionary[i].addr;
     

     return_ptr = sbrk(size);

     dictionary[dictionary_ct].addr = return_ptr;
     dictionary[dictionary_ct].size = size;
     dictionary[dictionary_ct].freed = 0;
     dictionary_ct++;

     return return_ptr;


void free(void *ptr)

    int i;

    if (!dictionary)
        return;

    for (i = 0; i < dictionary_ct; i++ )
    
        if (dictionary[i].addr == ptr)
        
            dictionary[i].freed = 1;
            return;
        
    

这不是一个很好的malloc() 实现。事实上,大多数malloc/free 的实现都会为malloc 返回的每个块分配一个小的标头。例如,标头可能从比返回的指针少八 (8) 个字节的地址开始。在这些字节中,您可以存储指向拥有该块的mem_dictionary 条目的指针。这避免了free 中的 O(N) 操作。您可以通过实现释放块的优先级队列来避免malloc() 中的 O(N)。考虑使用以块大小为索引的二项式

【讨论】:

对不起,我对 C 比较陌生,但是 malloc() 中的字典变量是什么? @no92 -- 我应该将其命名为“期刊”而不是“字典”。请记住,这是我的 examplemalloc 的简单实现。它至少有一个明显的缺陷:一次分配的块永远不能超过 1024 个。给出这个例子的唯一目的是给读者一个起点来实现他们自己的malloc。这只是实现malloc/free 库的概念基础。它甚至没有将realloc 作为另一个明显的缺陷来实现。它甚至可能不是最好的例子。 :)【参考方案3】:

我从 Sylvain 的回复中借用了代码。他似乎错过了计算 free_block* ini 的大小来计算开销。

总体而言,代码通过将此 free_block 作为标题添加到分配的内存来工作。 1. 当用户调用 malloc 时,malloc 返回有效载荷的地址,就在这个头部之后。 2. 调用free时,计算块头的起始地址(通过从块地址中减去头大小)并将其添加到空闲块池中。

typedef struct free_block 
    size_t size;
    struct free_block* next;
 free_block;

static free_block free_block_list_head =  0, 0 ;

// static const size_t overhead = sizeof(size_t);

static const size_t align_to = 16;

void* malloc(size_t size) 
    size = (size + sizeof(free_block) + (align_to - 1)) & ~ (align_to - 1);
    free_block* block = free_block_list_head.next;
    free_block** head = &(free_block_list_head.next);
    while (block != 0) 
        if (block->size >= size) 
            *head = block->next;
            return ((char*)block) + sizeof(free_block);
        
        head = &(block->next);
        block = block->next;
    

    block = (free_block*)sbrk(size);
    block->size = size;

    return ((char*)block) + sizeof(free_block);


void free(void* ptr) 
    free_block* block = (free_block*)(((char*)ptr) - sizeof(free_block ));
    block->next = free_block_list_head.next;
    free_block_list_head.next = block;

【讨论】:

谢谢,我认为这个回复比 Sylvain 的回复稍微正确一些,因为我只是想知道这个。开销变量是一个非常好的主意,只是没有正确计算甚至没有使用。 谁能告诉我在malloc函数中head的使用? (free_block** head = &amp;(free_block_list_head.next);) 我没有看到它在任何地方使用。另外,为什么要在返回前加上sizeof(free_block) head 是指向包含指针的地址的指针。它在 while 循环中用于取消链接返回给用户的内存块。添加和减去sizeof(free_block) 是一种常见而巧妙的技巧,可以向调用者“隐藏”元数据。 free() 实现中还有一个小错误,因为free(NULL) 会出现段错误。 您好,可以为这个实现添加合适的realloc函数吗?

以上是关于malloc 实现?的主要内容,如果未能解决你的问题,请参考以下文章

malloc 实现?

malloc基本实现

对齐 malloc 实现的解释

C语言 malloc()函数 分配内存空间尺寸的问题

C语言 malloc()函数 分配内存空间尺寸的问题

malloc和free的实现