Implement malloc using C/C++

Fork me on GitHub
  

Note: 本文基本上是Malloc Tutorial的整理和翻译。

malloc是做什么的?

mallocC程序中用来分配内存的一个库函数。C++里的new也是调用malloc实现的。为了清楚内存是怎么分配的,我们用C来实现一个简单的malloc函数.

malloc,brk以及sbrk系统调用

首先,我们知道计算机内存一般是分为下面几部份的:

  • Text Segment : 程序本身
  • Data Segment : 所有初始化的全局变量和静态变量
  • BSS Segment : 所有未初始化的全局变量
  • Heap Segment : 程序中动态分配的内存区
  • Stack Segment : 程序中局部变量的内存区

计算机内存组织一般如下图所示: memory organization

而其中Heap就是用malloc分配的内存,一般分为mapped region, unmapped region, 如下图所示: Heap organization

malloc函数可以在Heap Segmentunmapped region区域分配指定大小的内存并返回起始地址。 清楚了内存和malloc的关系,我们可以定义malloc的函数原型如下:

void* malloc(size_t size);

从上面Heap Segment示意图中能够知道Heap Segment是由starting address, break pointmaximum limit定义的一段连续内存空间。如果我们在Heap里分配内存,那我们必须知道break point的地址。我们可以借助brksbrk系统调用。

  • brkbreak point设置到指定的地址,如果成功返回0,否则返回1。
  • sbrk则将break point向前移动指定的字节大小。失败则返回(void*)-1,执行成功后的返回值根据不同实现而不同。

当增加地址为零时(sbrk(0)),返回值是实际的break point地址

brk以及sbrk的函数原型

int brk(const void *addr);
void* sbrk(intptr_t incr);

malloc实现

有了上面背景知识,我们可以开始一步一步实现malloc

一个简单的malloc实现

首先,借助于上面的sbrk我们可以很容易想到一个实现malloc的方法,即首先用sbrk(0)得到break point地址,然后调用sbrk来调整大小,如果成功则返回之前得到的break point的地址。代码如下:

#include <sys/types.h>
#include <unistd.h>

void *malloc(size_t size) {
  void *p;
  p = sbrk (0);
  /* If sbrk fails , we return NULL */
  if (sbrk(size) == (void*)-1)
    return NULL;
  return p;
}

一个简单的malloc函数就这么实现了。我们可以用这个简易版malloc来动态分配内存。但是很快问题就出现了,分配的内存我们没有办法去释放,还需要继续改进。

更实际的malloc实现

  • 首先,为了让我们malloc的memory能够free,我们需要定义free含义,如果一块memory能够再次被malloc分配使用,我们就说这块memory是free掉的。这样,我们很容易就想到使用一个结构体来保存当前分配memory的大小,释放等信息。
typedef struct s_block *t_block;

struct s_block {
  size_t size;      // size of current block
  t_block next;     // pointer to next block
  int free;         // sign to indicate free or not
};

#define BLOCK_SIZE sizeof(struct s_block)

void *base=NULL;  // global pointer to the 

需要注意的是我们每次调用sbrk的时候需要加上s_block的大小。使用s_block之后Heap Segment是下面这样分布的:

Heap List

  • 有了上面的s_block之后,每次需要分配内存时我们简单的遍历s_block找到free并且大小满足的s_block就可以了。
t_block find_block(t_block *last , size_t size) {
  t_block b=base;     // start from the heap starting point
  while (b && !(b->free && b->size >= size)) {
    *last = b;
    b = b->next;
  }
  return (b);
}
  • 如果找不到合适的s_block,我们需要调用sbrk()来分配内存
t_block extend_heap(t_block last , size_t s){
  t_block b;
  b = sbrk (0);
  if (sbrk(BLOCK_SIZE + s) == (void*)-1)
    /* sbrk fails , go to die */
    return (NULL);
  b->size = s;
  b->next = NULL;
  if (last)
    last ->next = b;
  b->free = 0;
  return (b);
}

malloc实现

根据之前的思路,为了实现malloc需要下面这些操作:

  • 如果是第一次调用malloc,则调用extend_heap来分配内存并且设置全局变量base
  • 否则就调用find_block
void *malloc(size_t size) {
  t_block b,last;
  if (base) {
    /* First find a block */
    last = base;
    b = find_block(&last ,s);
    if (b) {
      b->free=0;
    } else {
      /* No fitting block , extend the heap */
      b = extend_heap(last ,s);
      if (!b)
        return(NULL);
    }
  } else {
    /* first time */
    b = extend_heap(NULL ,s);
    if (!b)
      return NULL;
    base = b;
  }
  return b+1;
}

calloc,free,ralloc实现

malloc类似函数还有:

  • calloc :除了实现malloc功能之外,还会初始化分配内存的值
void *calloc(size_t nelem, size_t size) {
  size = nelem * size;
  void *ptr = malloc(size);
  memset(ptr, 0, size);
  return p;
}
  • free : 释放malloc,callocralloc分配的内存区域,简单的实现方式就是将s_block里的free标志位置为1
void *get_block_ptr(void *ptr) {
  return (t_block)ptr - 1;
}

void free(void *ptr) {
  if( !ptr )
    return;
  
  t_block p = get_block_ptr(ptr);
  assert(p->free == 0);
  p->free = 1;
}
  • ralloc : 调整之前分配的内存大小, 对于新的size小于当前size的,我们可以直接返回,否则重新分配并释放之前的内存
void *ralloc(void *ptr, size_t size) {
  if( !ptr )
    return malloc(size);
  
  // if request size is smaller than current size, just return
  t_block p = get_block_ptr(ptr);
  if( p->size >= size )
    return ptr;
    
  // or request size is larger than current size, we need
  // malloc the new size, copy old data and free old space
  void *new_ptr = malloc(size);
  if( !new_ptr )
    return NULL;
  memcpy(new_ptr, ptr, p->size);
  free(ptr);
  return new_ptr;
}

总结

上面实现了一个基本功能的malloc, 不过还有一些功能还需要完善,比如memory 对齐,分配时memory split和释放时memory fragment处理等

参考

  
志飞 /
Published under (CC) BY-NC-SA