与K&R实施malloc混淆

scr*_*eeb 8 c malloc kernighan-and-ritchie

我一直在阅读K&R,并在实施malloc()包含时遇到了一些困惑.

typedef long Align; /* for alignment to long boundary */

union header { /* block header */
  struct {
    union header *ptr; /* next block if on free list */
    unsigned size; /* size of this block */
  } s;
  Align x; /* force alignment of blocks */
};
typedef union header Header;

static Header base; /* empty list to get started */
static Header *freep = NULL; /* start of free list */

void *malloc(unsigned nbytes) {
    Header *p, *prevp;
    Header *morecore(unsigned);
    unsigned nunits;

    nunits = (nbytes+sizeof(Header)-1)/sizeof(Header) + 1;
    if ((prevp = freep) == NULL) { /* no free list yet */
        base.s.ptr = freep = prevp = &base;
        base.s.size = 0;
    }
    for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) {
        if (p->s.size >= nunits) {    /* big enough */
            if (p->s.size == nunits) {    /* exactly */
                prevp->s.ptr = p->s.ptr;
            } else {    /* allocate tail end */
                p->s.size -= nunits;
                p += p->s.size;
                p->s.size = nunits;
            }
            freep = prevp;
            return (void *)(p+1);
        }
        if (p == freep)    /* wrapped around free list */
            if ((p = morecore(nunits)) == NULL)
                return NULL;    /* none left */
    }
}

#define NALLOC 1024 /* minimum #units to request */
/* morecore: ask system for more memory */

static Header *morecore(unsigned nu)
{

  char *cp, *sbrk(int);
  Header *up;

  if (nu < NALLOC)
    nu = NALLOC;

  cp = sbrk(nu * sizeof(Header));

  if (cp == (char *) -1) /* no space at all */
    return NULL;

  up = (Header *) cp;
  up->s.size = nu;
  free((void *)(up+1));

  return freep;
}

/* free: put block ap in free list */
void free(void *ap) {
  Header *bp, *p;

  bp = (Header *)ap - 1; /* point to block header */
  for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
    if (p >= p->s.ptr && (bp > p || bp < p->s.ptr))
      break; /* freed block at start or end of arena */

  if (bp + bp->s.size == p->s.ptr) {
    bp->s.size += p->s.ptr->s.size;
    bp->s.ptr = p->s.ptr->s.ptr;
  } else
      bp->s.ptr = p->s.ptr;

  if (p + p->s.size == bp) {
    p->s.size += bp->s.size;
    p->s.ptr = bp->s.ptr;
  } else
    p->s.ptr = bp;
  freep = p;
}
Run Code Online (Sandbox Code Playgroud)

我无法理解的是尾部是如何分配的.

  • prevp->s.ptr仍然指向块的开始(但现在大小- nunits)将该块分区,所以该块的剩余部分仍然是可用的后?
  • 当递增(p += p->size)到新的,待返回的块开始的位置时,我们如何能够引用p->s.size和分配它nunits.不p应该NULL,因为除了标题之外没有初始化块?

dbu*_*ush 5

第一次调用 时malloc,通过调用 物理分配内存块sbrk并将其添加到空闲列表中。此时内存看起来是这样的:

 1 (base)              freep
 -----------           -------
 | 10 / 0  |           |  1  |
 -----------           -------

 10        11        12        13        14        15
 -------------------------------------------------------------
 |  1 / 6  |         |         |         |         |         |
 -------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

为了简单起见,我用“单位”而不是实际字节对内存块进行了编号。这里我们假设全局变量base位于地址 1,并且从地址 10 开始分配了一个由 6 个单元组成的块。在每个单元中,第一个数字是 的值ptr,第二个数字是 的值size。此外,还freep包含空闲块的起始地址。

因此,空闲列表从空基块开始,然后指向地址 10 处的块。该块包含 5 个空闲单元并指向基块。

假设当前调用请求malloc2 个单位。当我们第一次进入for循环时,prev == 1 并且p == 10

 1 (base)              freep       prev       p
 -----------           -------     ------     ------
 | 10 / 0  |           |  1  |     | 1  |     | 10 |
 -----------           -------     ------     ------

 10        11        12        13        14        15
 -------------------------------------------------------------
 |  1 / 6  |         |         |         |         |         |
 -------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

在尾部添加时,第一条语句是p->s.size -= nunits;sizep减少nunits

 1 (base)              freep       prev       p
 -----------           -------     ------     ------
 | 10 / 0  |           |  1  |     | 1  |     | 10 |
 -----------           -------     ------     ------

 10        11        12        13        14        15
 -------------------------------------------------------------
 |  1 / 4  |         |         |         |         |         |
 -------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

所以现在地址 10 处的块的大小是 4 个单位,而不是 6 个单位。接下来是将top += p->s.size;的值相加:sizep

 1 (base)              freep       prev       p
 -----------           -------     ------     ------
 | 10 / 0  |           |  1  |     | 1  |     | 14 |
 -----------           -------     ------     ------

 10        11        12        13        14        15
 -------------------------------------------------------------
 |  1 / 4  |         |         |         | xx / xx |         |
 -------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

现在p指向地址14。当前ptrsize字段包含垃圾,此处表示为“xx”。请注意,该值pNULL。它指向某个地方,但该内存没有有意义的数据。

现在我们运行p->s.size = nunits;它设置指向的size单元中的字段:p

 1 (base)              freep       prev       p
 -----------           -------     ------     ------
 | 10 / 0  |           |  1  |     | 1  |     | 14 |
 -----------           -------     ------     ------

 10        11        12        13        14        15
 -------------------------------------------------------------
 |  1 / 4  |         |         |         | xx / 2  |         |
 -------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

然后我们将freep = prevp;空闲指针设置为 的当前值prev。此时,没有任何变化。然后我们将p+115 返回给调用者。

最终结果是 by 指向的块prev没有改变,但是 by 指向的块prev->s.ptr现在小了所请求的单元数,并且地址 14 现在是已分配的大小为 2 个单元的内存块的开始位置。

当地址 15 稍后被传递到 时free,它会查看地址 14 以查看分配的块有多大,以便它可以将这些单元放回到空闲列表中。