与K& R

时间:2018-03-12 00:35:56

标签: 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;
}

我理解的是尾部是如何分配的。

  • 在对块进行分区后,prevp->s.ptr是否仍然指向块的开头(但现在有大小 - nunits),因此块的剩余部分仍然可用?
  • 当递增(p += p->size)到新的待返回块开始的位置时,我们如何引用p->s.size并为其分配nunits。不应该pNULL,因为除了标题之外,该块还没有初始化吗?

2 个答案:

答案 0 :(得分:3)

在第一次调用malloc时,通过调用sbrk物理分配内存块并添加到空闲列表中。此时内存如下所示:

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

 10        11        12        13        14        15
 -------------------------------------------------------------
 |  1 / 6  |         |         |         |         |         |
 -------------------------------------------------------------

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

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

假设当前对malloc的呼叫正在请求2个单位。当我们首次输入for循环时,prev == 1p == 10

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

 10        11        12        13        14        15
 -------------------------------------------------------------
 |  1 / 6  |         |         |         |         |         |
 -------------------------------------------------------------

在尾部添加时,第一个语句是p->s.size -= nunits;。这会使size中的nunits递减 1 (base) freep prev p ----------- ------- ------ ------ | 10 / 0 | | 1 | | 1 | | 10 | ----------- ------- ------ ------ 10 11 12 13 14 15 ------------------------------------------------------------- | 1 / 4 | | | | | | -------------------------------------------------------------

p += p->s.size;

所以现在地址10的块大小是4个单位而不是6个。 接下来是size,其将p的值添加到 1 (base) freep prev p ----------- ------- ------ ------ | 10 / 0 | | 1 | | 1 | | 14 | ----------- ------- ------ ------ 10 11 12 13 14 15 ------------------------------------------------------------- | 1 / 4 | | | | xx / xx | | -------------------------------------------------------------

p

现在ptr指向地址14.目前sizep字段包含垃圾,此处表示为“xx”。请注意,p->s.size = nunits; 为NULL。它指向某个地方,但该内存没有有意义的数据。

现在我们运行size,在p指向的单元中设置 1 (base) freep prev p ----------- ------- ------ ------ | 10 / 0 | | 1 | | 1 | | 14 | ----------- ------- ------ ------ 10 11 12 13 14 15 ------------------------------------------------------------- | 1 / 4 | | | | xx / 2 | | ------------------------------------------------------------- 字段:

freep = prevp;

然后我们将prev设置为指向当前值p+1的空闲指针。此时,没有变化。然后我们将prev(即15)返回给调用者。

最终结果是prev->s.ptr指向的块没有改变,但free指向的块现在小于请求的单元数,而地址14现在是开始分配2个单位的内存块。

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

答案 1 :(得分:1)

如果malloc()将使用自由分段的一部分,则将其分为两部分。第一个是免费的,第二个是返回的。我想你已经有了。

那么,后果是什么:

  • prev->s.ptr仍然指向相同的地址(原始的p),因为此处仍有空闲内存,只有nunits少于之前。
  • 在标题'原始p'中,地址s.ptr保持不变,因此prev->s.ptr->s.ptr仍然指向与之前相同的地址,链仍然有效(前两个项目符号应回答第一个问题)。
  • 现在p递增以指向我们想要返回的内存块的开头(p += p->s.size)。当然,该内存块也应该以尚未初始化的头开头,因此我们设置p->s.size = nunits; p->s.ptr可能仍然包含垃圾值,但这没关系,因为它不是一个空闲块(应该回答第二个问题)。
  • 最后返回p + 1,这是新标题后面的内存块的“有效负载”。