読者です 読者をやめる 読者になる 読者になる

mallocとfreeの実装

ガベージコレクションのアルゴリズムと実装の参考資料でもあるminigcmalloc/free部分の実装を抜き出して、自分でも書いてみました。sbrk(2)は呼び出すたびに、ヒープ領域を伸ばします。my_freeは、ヒープ領域を解放するわけではなく、my_freeしたヒープ領域を再利用できるようにfree_listに追加しています。my_malloc(size_t req_size)はfree_listの中からreq_size以上のメモリ領域を探します。見つかった場合は、そこからreq_sizeだけ確保します。もしも、無い場合は必要な大きさだけ、ヒープ領域を確保してから、その領域をfree_listに追加(sbrkした領域をmy_freeする)してから検索し直します。ただし、はじめてmy_mallocを呼ぶとき(free_list==NULL)は、sbrkを使ってヒープ領域を確保してfree_listに追加してからreq_size以上のメモリ領域を探します。sbrkはunix系のシステムコールなので、windowsでは動きません。そこはまた調べてみようと思います。(gccなら動くみたいです。)

コード

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
struct Header {
  size_t flags;
  size_t size;
  struct Header* next_free;
};
static struct Header* free_list = NULL;
#define PTR_SIZE (size_t)(sizeof (void*))
#define HEADER_SIZE (size_t)(sizeof (struct Header))
#define NEXT_HEADER(x) (struct Header*)((size_t)(x+1) + x->size)
#define ALIGN(x,a) ((x + (a-1)) & ~(a-1))
#define TINY_HEAP_SIZE 0x4000

struct Header* add_heap(size_t size) {
void* p;
struct Header* align_p;
  if(size < TINY_HEAP_SIZE)
    size = TINY_HEAP_SIZE;
  if((p = sbrk(PTR_SIZE + HEADER_SIZE + size)) == (void*)-1)
    return NULL;
  align_p = (struct Header*)p;
  align_p->flags = 0;
  align_p->size = size;
  align_p->next_free = align_p;
  return align_p;
}
void my_free(void*);
struct Header* grow(size_t req_size) {
struct Header *cp;
  if((cp = add_heap(req_size)) == NULL)
    return NULL;
  my_free((void *)(cp+1));
  return free_list;
}

void* my_malloc(size_t req_size) {
  req_size = ALIGN(req_size, PTR_SIZE);
  if(req_size <= 0)
    return NULL;
struct Header *p, *prevp;
  if((prevp = free_list) == NULL) {
    if((p = add_heap(TINY_HEAP_SIZE)) == NULL)
      return NULL;
    prevp = free_list = p;
  }
  for(p = prevp->next_free; ; prevp = p, p = p->next_free) {
    if(p->size >= req_size) {
      if(p->size == req_size) 
        prevp->next_free = p->next_free;
      else {
        p->size -= HEADER_SIZE + req_size;
        p = NEXT_HEADER(p);
        p->size = req_size;
      }
      return (void*)(p+1); 
    }
    if(p == free_list)
      if((p = grow(req_size)) == NULL)
        return NULL;
  }
  return NULL;
}
void my_free(void* p) {
struct Header *target, *hit;
  target = (struct Header*)p -1;
  hit = free_list;
  while(1) {
    if(hit < hit->next_free && (hit < target && target < hit->next_free))
      break;
    if(hit >= hit->next_free && (hit < target || hit->next_free > target))
      break;
    hit = hit->next_free;
  }
  free_list = hit;
  if(NEXT_HEADER(target) == free_list->next_free) {
    target->size += free_list->next_free->size + HEADER_SIZE;
    target->next_free = free_list->next_free->next_free;
  } else {
    target->next_free = free_list->next_free;
  }
  
  if(NEXT_HEADER(free_list) == target) {
    free_list->size += target->size + HEADER_SIZE; 
    free_list->next_free = target->next_free;
  } else {
    free_list->next_free = target;
  }
}
void free_list_reset(void) {
struct Header* iter = free_list->next_free;
int i = 0;  
  if(NEXT_HEADER(free_list) != iter)i++;
  while(iter != free_list) {
    free_list->size += iter->size + HEADER_SIZE;
    struct Header* next = iter->next_free;
    if(NEXT_HEADER(iter) != iter->next_free)i++;
    iter = iter->next_free;
  }
  assert(i == 1);
  free_list->next_free = free_list;
}
void test(void);
int main(void) {
  test();
  return 0;
}

void align_test(void) {
size_t i, j;  
  for(i = 0; i < 10; i++)
    for(j = 1 + PTR_SIZE * i; j <= PTR_SIZE * (i+1); j++)
      assert(PTR_SIZE* (i+1) == ALIGN(j, PTR_SIZE));
}
void my_malloc_test1(void) {
// case:free_list == NULL
  assert(NULL == free_list);
  assert(NULL == my_malloc(0));
char* str = (char*)my_malloc(5);
  strcpy(str, "hello");
  assert(!strcmp("hello", str));
struct Header* hdr = (struct Header*)str -1;
  assert(ALIGN(5, PTR_SIZE) == hdr->size);
  assert(NEXT_HEADER(free_list) == hdr);
  assert(TINY_HEAP_SIZE - ALIGN(5, PTR_SIZE) - HEADER_SIZE == free_list->size);
  my_free(str);
  assert(TINY_HEAP_SIZE  == free_list->size);
// case: free_list != NULL

char* str2 = (char*)my_malloc(5);
  assert(!strcmp("hello", str2));
  strcpy(str2, "world");
  assert(!strcmp("world", str2));
  assert(TINY_HEAP_SIZE - ALIGN(5, PTR_SIZE) - HEADER_SIZE == free_list->size);

  my_free(str2);
  assert(TINY_HEAP_SIZE  == free_list->size);
}

void my_malloc_test2(void) {
// case: free_list != NULL
  assert(NULL != free_list);
char *str1, *str2;
  str1 = (char*) my_malloc(5);
  strcpy(str1, "hello");  
  str2 = (char*) my_malloc(5);
  strcpy(str2, "world");
  assert(!strcmp("hello", str1));
  assert(!strcmp("world", str2));
  assert(TINY_HEAP_SIZE - 2*ALIGN(5, PTR_SIZE) - 2*HEADER_SIZE == free_list->size);
  my_free(str1);
  struct Header* hdr1 = (struct Header*)str1 -1;
  assert(free_list->next_free == hdr1);
  assert(hdr1->next_free == free_list);
  assert(TINY_HEAP_SIZE - 2*ALIGN(5, PTR_SIZE) - 2*HEADER_SIZE == free_list->size);
  assert(ALIGN(5, PTR_SIZE) == hdr1->size);
  my_free(str2);
  assert(TINY_HEAP_SIZE == free_list->size);
}

void my_malloc_test3(void) {
{
//  assert(NULL != free_list);
void *p1, *p2, *p3;
  p1 = my_malloc(TINY_HEAP_SIZE/4 - HEADER_SIZE);
  p2 = my_malloc(TINY_HEAP_SIZE/4 - HEADER_SIZE);
  p3 = my_malloc(TINY_HEAP_SIZE/4 - HEADER_SIZE);
  assert(TINY_HEAP_SIZE/4 == free_list->size);
  my_free(p2);
  struct Header* old = free_list;
  my_free(p1);
  assert(old->next_free == free_list);
  assert(free_list->next_free == old);
  assert((size_t)((struct Header*)p2 -1) == (size_t)free_list);
  assert(TINY_HEAP_SIZE/2 - HEADER_SIZE == free_list->size);
  my_free(p3);
  assert(old == free_list);
  assert(old->next_free == old);
  assert(TINY_HEAP_SIZE == free_list->size);
}

{
//  assert(NULL != free_list);
void *p1, *p2, *p3, *p4;
  p1 = my_malloc(TINY_HEAP_SIZE/4 - HEADER_SIZE);
  p2 = my_malloc(TINY_HEAP_SIZE/4 - HEADER_SIZE);
  p3 = my_malloc(TINY_HEAP_SIZE/4 - HEADER_SIZE);
  p4 = my_malloc(TINY_HEAP_SIZE/4 - HEADER_SIZE);
  assert(0 == free_list->size);
  struct Header* old = free_list;
  my_free(p3);
  assert(old == free_list);
  assert((size_t)((struct Header*)p3 -1) == (size_t)free_list->next_free);
  assert((size_t)((struct Header*)p3 -1)->next_free == (size_t)free_list);
  my_free(p1);
  assert((size_t)((struct Header*)p3 -1) == (size_t)free_list);
  assert((size_t)((struct Header*)p1 -1) == (size_t)free_list->next_free);
  assert((size_t)old == (size_t)free_list->next_free->next_free);
  assert(TINY_HEAP_SIZE/4 - HEADER_SIZE == free_list->size);
  my_free(p4);
  assert(old == free_list);
  assert(TINY_HEAP_SIZE/2 == free_list->size);
  assert(TINY_HEAP_SIZE/4 - HEADER_SIZE == ((struct Header*)p1 -1)->size);
  my_free(p2);
  assert(old == free_list);
  assert(TINY_HEAP_SIZE == free_list->size);
}
}
void reuse_heap_test(void) {
void *p1, *p2, *p3, *p4;
  p1 = my_malloc(10);
  p2 = my_malloc(20);
  p3 = my_malloc(30);
  my_free(p2);
  p4 = my_malloc(20);
  assert(p4 == p2);
  my_free(p1);
  my_free(p3);
  my_free(p4);
  assert(TINY_HEAP_SIZE == free_list->size);
}
void grow_test(void) {
  assert(TINY_HEAP_SIZE == free_list->size);
  struct Header* hdr = grow(ALIGN(10, PTR_SIZE));
  assert(hdr->next_free->size == TINY_HEAP_SIZE);
  void *p;
  p = my_malloc(TINY_HEAP_SIZE + 100);
  assert(hdr->next_free == free_list);
  assert(TINY_HEAP_SIZE + 100 == ((struct Header*)p -1)->size);
  my_free(p);
}
void test(void) {
  align_test();
  my_malloc_test1();
  my_malloc_test2();
  my_malloc_test3();
  reuse_heap_test();
  grow_test();
}