Kernighan & Ritchie malloc free logic

I have spent hours on one particular condition in free() implementation. I have searched the web and stackoverflow to see if anyone else discussed this, but i found none. I understand the general idea behind the 3 functions described in page 187 & 188 of the second edition (ANSI). I am a professional and most of the code makes sense to me, but i am stumped when i work out how all 3 functions would work for the very first call (in other words, initialization of *freep to something).

Here are the steps that i worked out from the code: Step 1. There are two global variables defined as below

typedef long Align;
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 */

STEP 2: Went through the malloc() function first, because the very first time, when the user calls malloc() the 2 globals given above will not contain any usable data. I have attached the code for malloc() from the book, below this section. I understand how 'nunits' is calculated. If freep == NULL, freep & prevp are made to point to &base. So, the for loop is entered for the very first time with p == prevp->s.ptr == base.s.ptr == freep == prevp == &base (sic). The next if condition is false because base.s.size has been set to zero during the very first call. Now, the next if condition (p == freep) is true because both are pointing still to the address of 'base'. So, at this point we make a call to morecore() which is described in step 3 below.

/* malloc: general-purpose storage allocator */
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 = freeptr = prevptr = &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 */
    } // end of for loop
}

STEP 3: morecore()

So far, all the pointers are pointing to &base (global) and we enter morecore() which calls the OS to get free memory space. morecore() then initializes the beginning of this memory to be the Header structure, as below:

   Header *up;
   up = the memory from OS
   up->s.size =  nu; (number of units, each of size = sizeof(Header))
   free( (void*) (up+1) );
   return freep;

After init, morecore() calls free() with argument that points to the next address (after the initial Header structure). morecore() does not change freep global, which still points to &base.

STEP 4 : free() is entered.

My question is in free function, so i give only the relevant lines below.

    void free( void* ap)
    {
         Header *bp, *p; // a pointer to base (ie. Header) and a temporary pointer 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.

The for loop is entered with p == freep == &base still. I have trouble in the conditionals after this point. The for loop conditional will be executed first - base (global) address (p== freep == &base still) is compared with the address of newly obtained space from OS (Note - this address comes through the argument of free). In malloc we initialized freep == p->s.ptr to &base. So, at this point both p and p->s.ptr are pointing to the same address. So, the for condition will be TRUE because bp can not be both > and < the same address. Note the negation, which makes the loop condition TRUE and we enter if condition.

STEP 5:

The first part of the if condition is p >= p->s.ptr. This is TRUE because p == freep == &base == p->s.ptr (which was set in malloc). The second part of if condition is TRUE because bp has to either > or < the same address(p == p->s.ptr). So, we break out of for loop because freed block is at start of arena (or end of arena, if we reach this point through the circularly linked list of free memories maintained by malloc).

STEP 6: my questions are here

if(bp + bp->s.size == p->s.ptr) {          // join to upper nbr
     bp->s.size  += p->s.ptr->s.size;
     bp->s.ptr  =   p->s.ptr->s.ptr;  
}
else  bp->s.ptr = p->s.ptr;

Continuing on...the first if condition after for loop is false because the newly allocated memory space is not going to equal p->s.ptr, which is address of 'base'. So, else is executed and the newly allocated header is set to p->s.ptr == freep->s.ptr == freep == &base !!! Why would this new pointer be set to the address of 'base' global ?

My second question is why the 2 level indirection (3rd line of code above) to set bp->s.ptr, in case if condition is satisfied ? (I understand this is to join contiguous blocks.)

STEP 7: join the lower nbr (is the comment from book)

 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;

The next if condition will also be false for the same reason, during the very first pass, and else clause sets p->s.ptr == base.s.ptr == freep->s.ptr to the newly alloced memory. So, the next statement freep = p is redundant during the very first time we go through this sequence of malloc/morecore/free ? At end of all these steps, freep still points to the address of 'base' ? No changes ? Feels like i have made some mistake in logic.

One point of clarification on what ANSI standard C says about unions. This goes back to the very first code snipet i have above with the 'typedef'. I think all the members inside a static union (like static Header base) are initialized by compiler to zero ? What if i didn't declare them as static ? Would the struct s.header ptr be NULL in this case ?

Please point out, if i made any errors in my logic of 7 steps, for the first pass. I would also appreciate, if someone took the time to write down clearly (the way i have done) how free() works and the pseudo code for the various conditionals within free(). This thread would then become very useful for students / newbies.

Thank you for your time and explanation.


ANSWERS:


Why would this new pointer be set to the address of 'base' global ?

In description of malloc realization, it's said that last block has pointer to the first block. And because of first invocation of function you don't have any blocks so this block points on itself.

So, the next statement freep = p is redundant during the very first time we go through this sequence of malloc/morecore/free ? At end of all these steps, freep still points to the address of 'base' ? No changes ? Feels like i have made some mistake in logic.

I believe, that there is no mistake in your reasoning about freep = p = &base in the first invocation of malloc. I think, generally this code is pretty tricky. In this question (Explain this implementation of malloc from the K&R book) you can find more critiques of k&r malloc() realization

But for your case I think name free() confused you a little. Because function free() actually has two purposes: initializing brand new free-list and obviously freeing previously allocated memory. Also "Code Complete" by S.Macconnell says that having function with several purposes is not good idea.

So in the first pass yes actually you don't need some additional assignments (initialize regime of free()). But in next invocations of free (regime of real freeing previously allocated memory) is useful to have pointer to last block of free-list memory for coalescing of memory.

One point of clarification on what ANSI standard C says about unions. This goes back to the very first code snipet i have above with the 'typedef'. I think all the members inside a static union (like static Header base) are initialized by compiler to zero ?

Yes, cite from c89 draft (3.5.7. Initialization):

If an object that has static storage duration is not initialized explicitly, it is initialized implicitly as if every member that has arithmetic type were assigned 0 and every member that has pointer type were assigned a null pointer constant. If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.


What if i didn't declare them as static ? Would the struct s.header ptr be NULL in this case ?

I believe it will be garbage.



 MORE:


 ? C malloc and free
 ? How do malloc() and free() work?
 ? How do malloc() and free() work?
 ? How do malloc() and free() work?
 ? How is malloc() implemented internally?
 ? How does C free() work?
 ? How are malloc and free implemented in C?
 ? how does dynamic memory allocation work
 ? free function not working in c / objective-c
 ? function to free memory of 1D Array