Skip to content

minigc読み

2023年9月10日

(追記)この記事あまりにも適当すぎるので書き直した。描き直したバージョンはこっち

ソースはこれ。本家からフォークした。適当に読んで予想で解釈しているので間違いだらけだと思う。

アロケーター部分

まずは構造体から見ていく。

flags

size

header *next_free

header型のnext_freeによってヘッダーがシングルリンクになっていることがわかる

GC_Heap *slot

size

flagsのマクロなど

#define FL_ALLOC 0x1

#define FL_MARK 0x2

#define FL_SET(x, f) (((Header *)x)->flags |= f)

#define FL_UNSET(x, f) (((Header *)x)->flags &= ~(f))

#define FL_TEST(x, f) (((Header *)x)->flags & f)

その他のマクロなど

#define TINY_HEAP_SIZE 0x4000

#define PTRSIZE ((size_t)sizeof(void *))

#define HEADER_SIZE ((size_t)sizeof(Header))

#define HEAP_LIMIT 10000

#define ALIGN(x, a) (((x) + (a - 1)) & ~(a - 1))

#define NEXT_HEADER(x) ((Header *)((size_t)(x + 1) + x->size))

size_tとは

static Header *free_list;

static GC_Heap gc_heaps[HEAP_LIMIT];

static size_t gc_heaps_used = 0;

アロケーター関数とでもしておこう add_heap mini_gc_malloc mini_gc_free gc処理が含まれている

ポインタの型はvoidなんだな

add_heap

if (gc_heaps_used >= HEAP_LIMIT) {
  fputs("OutOfMemory Error", stderr);
  abort();
}

ヒープの数がリミットを超えたらエラー

if (req_size < TINY_HEAP_SIZE)
    req_size = TINY_HEAP_SIZE;

ヒープの最小サイズに合わせる

p = malloc(req_size + PTRSIZE + HEADER_SIZE);
  if (!p)
    return NULL;

ボディサイズ+ポインタサイズ+ヘッダサイズをアロケートする PTRSIZEを足しているのはアラインメントした時に終端が溢れないようにするため。 ヒープ領域に対してもHeaderが付与されている。

align_p = gc_heaps[gc_heaps_used].slot = (Header *)ALIGN((size_t)p, PTRSIZE);
  req_size = gc_heaps[gc_heaps_used].size = req_size;
  align_p->size = req_size;
  align_p->next_free = align_p;
  gc_heaps_used++;

ヘッダを初期化している

align_p = gc_heaps[gc_heaps_used].slot = (Header *)ALIGN((size_t)p, PTRSIZE); これは

align_p->next_free = align_p;なのでヘッダーのリンクリストの終端は終端自身を指していることがわかる。

grow

growとは?

構造体のアドレス + 1をすると構造体の直後のアドレス、この場合はボディの部分が指される。

mini_gc_malloc

if ((prevp = free_list) == NULL) {
    if (!(p = add_heap(TINY_HEAP_SIZE))) {
      return NULL;
    }
    prevp = free_list = p;
  }

free_listがNULLだったら作る?

else {
        /* too big */
        p->size -= (req_size + HEADER_SIZE);
        p = NEXT_HEADER(p);
        p->size = req_size;

sizeが大きすぎる場合、一個前のブロックまで戻ってそこで次のヘッダを探す。で、そこのヘッダのブロックのsizeをreq_sizeにしてあげることでjust fitさせる。

prevpは今から割り当てるブロックの前のブロックを表している。なのでprevp->next_freeが次に割り当てされるブロックのヘッダを指している。

free_list = prevp;
FL_SET(p, FL_ALLOC);
return (void *)(p + 1);

prevpをfree_listに入れているのはなんでだ?

malloc時にGCしていることがわかる もっと詳しくいうとfree_listを辿り切った時(freeがなかった時)にGCする

if (p == free_list) {
      if (!do_gc) {
        garbage_collect();
        do_gc = 1;
      } else if ((p = grow(req_size)) == NULL)
        return NULL;
    }

free_listの扱いについてはmini_gc_freeを読むと理解できるはずだ。

mini_gc_free

target, hit

target = (Header *)ptr - 1;

*ptrはデータの先頭部分を指している -1することでデータのヘッダ部分を指すようにしている

for (hit = free_list; !(target > hit && target < hit->next_free); hit = hit->next_free)

free_listはソートされている のでtargeを入れる場所をfree_listから探している hitとhit->next_freeの間もしくは終端にtargetが入る

| hit | hit->next_free |

| hit | target | hit->next_free | | hit | target |

if (hit >= hit->next_free && (target > hit || target < hit->next_free)

free_listは双方向循環リスト? だからこの条件でリストの終端がわかる

そもそもなんでmergeしているかというとおそらくfragmentationを防ぐため。