7
\$\begingroup\$
  • Purpose: Educational
  • Requirement: Given 64KB of memory, implement your own malloc and free
  • Method: First-Fit
struct Header 
{
 unsigned short next; //in blocks of sizeof(Header)
 unsigned short size; //in blocks of sizeof(Header)
};
static Header* _dBuffer;
static Header* _dTail;
static public void dInit()
{
 _dBuffer = getMemBlock(64 * 1024);
 _dTail = _dBuffer;
}
static public void* dMalloc(unsigned short nbytes)
{
 if (_dTail == NULL) //empty free list
 return NULL;
 Header* prev = NULL;
 Header* current = _dTail;
 // below is equivalent to ceil((nbytes + sizeof(Header)) / sizeof(Header))
 int nblocks = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1; 
 while (current->next != 0 && current->size < nblocks)
 {
 prev = current;
 current = _dBuffer + current->next;
 }
 if (current->size < nblocks) 
 //we've reached the end of the free list without finding a chunk big enough 
 return NULL;
 if (current->size == nblocks) 
 {
 //we've found an exact match, remove the chunk from the free list
 if (prev == NULL) //remove the first chunk
 _dTail = NULL;
 else
 prev->next = current->next;
 return current + 1;
 }
 //current->size > nblocks
 current->size -= nblocks;
 current+= current->size;
 current->size = nblocks;
 return current + 1;
}
static bool coalesce(Header* block)
{
 Header* nextBlock = _dBuffer + block->next;
 if (block + block->size != nextBlock)
 return false;
 block->size += nextBlock->size;
 block->next = nextBlock->next;
 return true;
}
static public void dFree(void* p) 
{
 Header* released = (Header*)p - 1;
 Header* before = NULL;
 Header* after = _dTail;
 if (_dTail == NULL) //the free list is empty
 {
 released->next = 0;
 _dTail = released;
 return;
 }
 //find where released is located in the free list
 while (after->next != 0 && after < released) 
 {
 before = after;
 after = _dBuffer + after->next;
 }
 if (after < released) //released chunk after the end of the free list
 {
 released->next = 0;
 after->next = released - _dBuffer;
 coalesce(after);
 return;
 }
 if (before == NULL) //released chunk before the beginning of the free list
 {
 released->next = _dTail - _dBuffer;
 _dTail = released;
 coalesce(released);
 return;
 }
 //released chunk is somewhere in the middle of the list
 released->next = before->next;
 before->next = released - _dBuffer;
 if (coalesce(before)) //before was coalesced with released
 coalesce(before); //now try to coalesce it with after
 else
 coalesce(released); //otherwise, try coalescing released with after
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Dec 15, 2012 at 16:03
\$\endgroup\$
0

2 Answers 2

7
\$\begingroup\$

I think this is a bug:

if (current->size == nblocks) 
{
 //we've found an exact match, remove the chunk from the free list
 if (prev == NULL) //remove the first chunk
 _dTail = NULL;
 ^^^^^^^^^^^^^^^
 You are just removing the head of the list
 Not NULL(ing) the list.
 I think the line should be:
 _dTail = current->next;
 else
 prev->next = current->next;
 return current + 1;
}

The only thing I don't like is:

unsigned short next; //in blocks of sizeof(Header)

This because you have to keep doing maths to covert it to/from a pointer. I think your code would become much simpler if you just convert this too:

Header* next; // next block in list

I would also remove the assert:

assert(nbytes % sizeof(Header) == 0);

and replace it with code to move the size up.

if (nbytes % sizeof(Header) != 0)
{
 nbytes += (sizeof(Header) - (nbytes % sizeof(Header)));
}

I think you can simplify your code by quite a bit by using sentinel values on your list. See this answer about lists read the bit about sentinals and how it makes things easier.

answered Dec 15, 2012 at 17:45
\$\endgroup\$
2
  • \$\begingroup\$ I don't like the unsigned short next either, but this way I can have a Header of size 4 (rather than 6 or 10 depending on x86/x64). I totally agree that if I were handling a general amount of memory (as opposed to 64KB) a pointer would have made life much easier (I could also use it to store NULL) \$\endgroup\$ Commented Dec 15, 2012 at 18:21
  • \$\begingroup\$ Also your bug report is spot on - though I believe the correct line would be _dTail = (_dTail->next == 0) ? NULL : _dBuffer + _dTail->next \$\endgroup\$ Commented Dec 15, 2012 at 18:29
3
\$\begingroup\$

A few minor comments

  • I don't like the use of a leading underscore on variable names (reserved for system use I think)

  • I would add a little function to calculate the number of blocks instead of doing it twice (in getMemBlock and dMalloc). eg

    static inline size_t nBlocks(size_t nbytes)
    {
     return (nbytes + sizeof(Header) - 1) / sizeof(Header);
    }
    
  • There are many implicit conversion and sign conversions warnings resulting from your mixing of signed and unsigned variables. These can often cause subtle problems.

  • There are also various precision loss warnings resulting from the use of short integers in the header.

  • dInit needs a parameter list (void). Perhaps better to pass in the size. Also needs to return success/failure.

  • You have no record of the allocated area and no way of checking for validity in dFree. So I can do:

    int main(int argc, char **argv)
    {
     void *mem;
     dFree(mem);
     ...
    

    and it will work, adding some random location to the pool.

  • Also, I can call dFree without calling dInit and it will not fail.

answered Dec 18, 2012 at 13:35
\$\endgroup\$
3
  • \$\begingroup\$ Thanks so much for the input ! leading underscores I'm used to it from C# but you may be right about using them in C/C++. nBlocks good idea warnings I don't recall seeing any warning but I'll chcek again. dInit the assumption is that we're given 64KB, otherwise using short fields in the header won't work anyway. validity I agree about calling to dFree without calling dInit, but the only way I could check for validity would be statistical (insert some magic word in the header that may appear in the user data anyway). I had max space efficiency in mind but it's a possible tradeoff. \$\endgroup\$ Commented Dec 18, 2012 at 14:39
  • \$\begingroup\$ For warnings, it depends upon your compiler and which warnings you have enabled. To check validity, I imagined you could keep a pinter to the base and limit of the allocated memory and check the dFree parameter lies between the two. \$\endgroup\$ Commented Dec 18, 2012 at 18:53
  • \$\begingroup\$ Thanks, I've increased the warning level and I do see some warnings like you predicted, I'll check them out. Regarding validity, I agree this range check would be useful. A user would still be able to mess things up by calling dFree on an address inside the range not returned by dMalloc, but why not make that extra check when it's possible. Thanks again! \$\endgroup\$ Commented Dec 18, 2012 at 19:04

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.