Skip to main content

How to write your own Malloc and Free using C?


                               Here, I am going to present you one of the simplest and easy-to-understand code for the implementation of the Malloc and Free functions that are used for the Dynamic memory allocation and deallocation in C.
                               This code will work fine when run on a Linux operating system.

Click here to get the full code.

The concept behind it is,

(Quoted from Malloc Tutorial)


Let me explain it further.

                    Basic idea behind :- 

                                             The block of memory containing the metadata (data about data) is stored adjacent to the specific block of allocated memory about which it refers to. This metadata block, or more specifically the linked list structure (as shown in the above diagram), is required because we need to know whether a particular block is free or whether it is already being allocated and also to know the size of the block which it refers to.

            This is the metadata block structure,
                             
                          struct block{
size_t size;
int free;                             
struct block *next;
                          };               

              Here, I assumed a block of memory of size 20,000 bytes char memory[20000];  (assuming that the storage for a character is 1 byte in the machine) and all the data structures and the allocated memory blocks reside in this same chunk of memory. 


                Now I will explain the code, part by part :- 
      
This is the C code for the header file required.

mymalloc.h       (Name your header file in this way)

#include<stdio.h>
#include<stddef.h>

stddef.h is required because the definition for size_t datatype is found there.

char memory[20000];

This is the declaration of the array which is considered as our memory. We get a contiguous allocation of memory by using an array.

/*The structure definition to contain metadata of each block allocated or deallocated*/
struct block{
size_t size; /*Carries the size of the block described by it*/
int free;  /*This flag is used to know whether the block described by the metadata structure is free or not -- > if free, it is set to 1. Otherwise 0.*/
struct block *next; /*Points to the next metadata block*/
};

/*Pointing to the main block of memory which is initially free (no storage allocation yet)*/
struct block *freeList=(void*)memory;

Here we initialize a pointer of type "block", named freeList pointing to the starting address of the chunk of memory we created before. This freeList pointer will point to the start of the linked list of metadata blocks.The starting address of the array (memory) should be casted to type void so that we are able to allocate blocks of memory which are of different datatypes.(int, char, float etc.)

/*The function definitions which are defined in the next source file malloc.c*/
void initialize();
void split(struct block *fitting_slot,size_t size);
void *MyMalloc(size_t noOfBytes);
void merge();

void MyFree(void* ptr);

This is the C code for the implementation of the malloc and free functions

mymalloc.c        (Name your source file in this way)

#include<stdio.h>
#include<stddef.h>
#include "mymalloc.h" /*Here we include the header file given above*/

/*Initializing the block of memory*/
void initialize(){
freeList->size=20000-sizeof(struct block); 
freeList->free=1;
freeList->next=NULL;
}

This is where we initialize the first metadata block update it to refer to the next block of memory.

  • The size of the block that it refers to is (20000 bytes- the_size_of_one_metadata_block)
  • To indicate that the block is not yet allocated, we set the free flag to 1.
  • And the first metadata block has no next metadata block yet. So we set next to NULL.
/*Making way for a new block allocation by splitting a free block -- (Assume first fit algorithm)*/
void split(struct block *fitting_slot,size_t size){
struct block *new=(void*)((void*)fitting_slot+size+sizeof(struct block));
new->size=(fitting_slot->size)-size-sizeof(struct block);
new->free=1;
new->next=fitting_slot->next;
fitting_slot->size=size;
fitting_slot->free=0;
fitting_slot->next=new;
}

               We use the First-fit-algorithm to find a free block to allocate memory. Assume that we get a request to allocate a block of memory of size 500 bytes. Starting from the first metadata block we can go on searching for the first block which has enough space to allocate. If we get the free block sizes to be 250, 130 and 750 in order, we will allocate the block of size 750.

               If we find a free block which exactly fits the required size, we don't need to do the splitting. So this function is only required if we have what is more than required.

              The arguments accepted by this function are --- A pointer to the metadata block which refers to the block of memory whose size is more than required.(fitting_slot) and the required size of the memory chunk to be allocated.

               Here we create a new metadata block pointer called "new". And it should point to the location provided by passing(setting aside) a block of memory which is equal to the_size_of_the_metadata_block_we_considered + the_size_requested_to_be_allocated

               Then this new pointer points to the metadata block referring to the next free chunk.
As you can see from the code, I have set the attributes of both the new and fitting_slot metadata blocks accordingly.

 Note: The malloc and free functions are named here as MyMalloc() and MyFree(). 

/*Function MyMalloc(malloc)*/
void *MyMalloc(size_t noOfBytes){
struct block *curr,*prev;

We need metadata block pointers to traverse through the freeList.

void *result;

This result pointer is to return the starting address of the allocated chunk of memory.

if(!(freeList->size)){ 
initialize();
printf("Memory initialized\n");
}

This condition will initialize the memory if it is not initialized at the beginning. This set of statements will be executed only if the size of the first metadata block is not set yet. That means, if the memory is still not initialized.

curr=freeList;

Now we make the temporary pointer curr to pint to the start of the metadata block list.

while((((curr->size)<noOfBytes)||((curr->free)==0))&&(curr->next!=NULL)){

If this condition is met, the metadata block we checked cannot be used for the allocation. So we execute the following statements. That is you go on checking one metadata block at a time.

prev=curr;
curr=curr->next;
printf("One block checked\n");
}

if((curr->size)==noOfBytes){

If this condition is met, the metadata block we checked refers to a chunk of memory that exactly fits the required size. So set the free flag to 0, indicating that it is allocated. Then return the starting address of the block of allocated memory.

curr->free=0;
result=(void*)(++curr);
printf("Exact fitting block allocated\n");
return result;
}


else if((curr->size)>(noOfBytes+sizeof(struct block))){

If this condition is met, the metadata block we checked refers to a chunk of memory that is of size greater than what is required. So invoke the split() function to allocate only the block which is required and then return the starting address of the block of allocated memory.

split(curr,noOfBytes);
result=(void*)(++curr);
printf("Fitting block allocated with a split\n");
return result;
}

Else it means that there is no sufficient memory to allocate so you should return a NULL pointer.

else{
result=NULL;
printf("Sorry. No sufficient memory to allocate\n");
return result;
}
}

        There can be a situation where you have consecutive blocks that are set free by deallocating after they were previously allocated. This results in external fragmentation which will cause the MyMalloc() function to return a NULL pointer although we have enough memory to allocate. Therefore we use a function called merge() to join the consecutive free blocks by removing the metadata blocks lying in between.

/*This is to merge the consecutive free blocks by removing the metadata block in the middle. This will save space.*/
void merge(){
struct block *curr,*prev;
curr=freeList;
while((curr->next)!=NULL){
if((curr->free) && (curr->next->free)){
curr->size+=(curr->next->size)+sizeof(struct block);
curr->next=curr->next->next;
}
prev=curr;
curr=curr->next;
}
}

After we allocate memory for some purpose, it is a really good practice to deallocate that memory if you have finished using it, so that it can be used by another person for his memory requirement.
We use the MyFree() function for that.

 It takes the pointer to a block of memory previously allocated as a parameter.

/*Function MyFree(free)*/
void MyFree(void* ptr){
if(((void*)memory<=ptr)&&(ptr<=(void*)(memory+20000))){

             Here we check whether the address to which the pointer given as an argument to the function actually lies within the address range of the memory array that we used for the purpose. If yes,we simply set the free flag in the metadata block to 1 indicating that it is free and scan through and merge the consecutive blocks that are free, if any.

struct block* curr=ptr;
--curr;
curr->free=1;
merge();
}
else printf("Please provide a valid pointer allocated by MyMalloc\n");
}

If a valid pointer is not provided, the above message will be printed.

So that is the detailed explanation of the code. (You can click the link given at the top of the page to get access to the full code)

I hope you got a better understanding on how to implement your own malloc and free functions using C.

                                                                  Thank you!


Comments

  1. This is great and easy to understand. thank you very much. :)

    ReplyDelete
  2. hy, this is good tutorial, but megre fucntion is broken. if you have 1 data in memory : curr->next=curr->next->next;
    curr->next->next = 0 !! and when see curr address in while, curr address is NULL, and program will die, if NULL->next..

    ReplyDelete
    Replies
    1. megre function:
      while ( curr && curr->next ) { ... }
      :D

      Delete
  3. thank you for saving me

    ReplyDelete
  4. If the realloc and free function also be implemented in it?

    ReplyDelete
    Replies
    1. I could be wrong (I haven't even tested the code as is), but I think this could work as a realloc function:
      ```void *realloc(void* ptr, unsigned long size) {
      void* newPtr = malloc(size);
      memcpy(ptr, newPtr, ((struct block*)ptr)->size);
      free(ptr);
      return newPtr;
      }```
      The free function is already part of the article

      Delete
    2. Good Point. "curr=curr-next " should be in else part of the above if.

      Delete
  5. Really good post; easy to understand and well written. Thanks a lot!

    ReplyDelete
  6. well, thanks for making chained lists a thing that i can explain and utilise <3

    ReplyDelete
  7. Great, I liked simple idea. I have one suggestion. In marge(), if there are 4 consecutive free blocks, it will still result in 2 consecutive free blocks. I would make merge recursive to make sure all consecutive blocks have been merged.

    I only looked at your explanation here, so pardon me if you have already done that in .c.

    ReplyDelete
    Replies
    1. I haven't looked in detail, but it looks like merge is called every time you free a block. So basically:
      1. You start with one block
      2. You call malloc, creating another block (there are now 2 blocks)
      3. You call free on your block once you've finished with it, merging it back into freeList (there is now one block again)
      However many blocks you create, they will all be merged back into freeList when they are freed.

      Delete
  8. Hi Tharika,

    Thanks a lot for post.

    ReplyDelete
    Replies
    1. Hi,

      Thanks for your post! Very nicely done and explained.
      Just one comment... I think you are missing a continue in your merge function, inside the if condition, as you want to continue and merge also the next available free block. Such a case can be possible if you freed a block which is in between two other free blocks (so now you have a sequence of 3 free blocks which you want to merge into one).

      Delete
    2. Yes just put the curr=curr->next in an else statement and it works fine. You should only go to the next block if the current block and next block cannot be merged

      Delete
  9. Hello, thanks for the example. If I want to stored any type of data on any block, how can I do that?

    Nice Post!!

    ReplyDelete
  10. Thanks for an awesome tutorial. However, Is this algorithm best fit or first fit, please?

    ReplyDelete
  11. I need help with understanding split function plz, anyone

    ReplyDelete
  12. thank you & great work keep it up

    ReplyDelete
  13. What does --curr and ++curr actually do?

    ReplyDelete
  14. Char memory[20000];
    Allocates memory in stack not in heap.

    ReplyDelete
    Replies
    1. --curr is pre decrement operation. ie first decrement the pointer and then do operation of that address

      similarly ++curr is pre increment, ie first increment the pointer and then do the operation on the new memory address.

      Delete
  15. great explanation !
    one query in
    struct block *new=(void*)((void*)fitting_slot+size+sizeof(struct block));
    should it not be like below:-
    fitting_slot++;
    struct block *new=(void*)((void*)fitting_slot+size+sizeof(struct block));

    if start without incrementing fitting_slot, it will result in less memory.

    ReplyDelete
  16. Excellent post. In merge, if you want to keep merging multiple free blocks starting from freelist, we should not move curr to curr->next. Only move curr->next to curr->next->next and keep updating curr's free size.

    ReplyDelete
  17. Hi, would you please explain every line of the split() function? I don't understand any of it. You said "new" points to a block of memory which is equal to the_size_of_the_metadata_block_we_considered + the_size_requested_to_be_allocated. But in the code there are three operands, namely fitting_slot+size+sizeof(struct block), whereas in your explanation you have only two operands, namely considered+requested? So, I would appreciate if you explain this and every other line in the split() function. Thanks.

    ReplyDelete
  18. Hi, why increment curr before assigning it to the result? I'm talking about this.

    result=(void*)(++curr);

    ReplyDelete
    Replies
    1. Here, curr is pointing to metadata. So we need to return pointer next to metadata byte.

      Delete
  19. Amazing explanation. Thanks for such a post. Was really curious how we can do that and the post explained everything in detail.

    ReplyDelete
  20. I think "new->next=fitting_slot->next;" in the split function should point to the head of the next metaData, or if it's at the end of the Heap address, it should be NULL

    ReplyDelete
  21. Thanks for the post. A note -> arithmetic to a void pointer is illegal.

    ReplyDelete
  22. Great post! just one change needed - in http://tharikasblogs.blogspot.com/p/include-include-include-mymalloc.html

    replace below

    int *p=(int)MyMalloc(100*sizeof(int));
    char *q=(char)MyMalloc(250*sizeof(char));
    int *r=(int)MyMalloc(1000*sizeof(int));
    char *w=(char)MyMalloc(700);

    with

    int *p=(int *)MyMalloc(100*sizeof(int));
    char *q=(char *)MyMalloc(250*sizeof(char));
    int *r=(int *)MyMalloc(1000*sizeof(int));
    char *w=(char *)MyMalloc(700);
    int *k=(int *)MyMalloc(500*sizeof(int)

    ReplyDelete
  23. Also it would be good to extend this article to deal with locks to manage the freelist

    ReplyDelete
  24. Thanks for the detailed post. I was able to test it out. The only corrections I made were as follows:
    - In the following condition check, changed the condition to >= instead of >.
    else if((curr->size)>(noOfBytes+sizeof(struct block))){
    - In merge() function, put the curr pointer update in else as shown below:
    else {
    prev=curr;
    curr=curr->next;
    }
    - Also updated the main() function as shown below, to test the corner cases.
    int *p=(int *)MyMalloc(100*sizeof(int));
    char *q=(char *)MyMalloc(250*sizeof(char));
    char *r=(char *)MyMalloc(250*sizeof(char));
    char *u=(char *)MyMalloc(500);
    int *s=(int *)MyMalloc(1000*sizeof(int));
    MyFree(q);
    MyFree(u);
    MyFree(r);
    char *t=(char *)MyMalloc(500);
    MyFree(s);
    int *k=(int *)MyMalloc(5000*sizeof(int));
    printf("Allocation and deallocation is done successfully!");

    ReplyDelete

Post a Comment

Popular posts from this blog

How to connect my database instance with elastic beanstalk instance in AWS?

If you have deployed your web application in Elastic Beanstalk in AWS and now you need to connect a database to this instance, and your database is actually residing in a different instance, how can you actually connect them? It's easy with Elastic Beanstalk. I will explain an example scenario that I used for connecting my elastic beanstalk web application with another instance containing my MongoDB database. By looking at this, you can customize as per your need. Don't worry. This is easy. :) The only things you need here are the details about the 1. Database name that you need to connect to. Ex:- "myDB" 2. Port at which the database instance is listening. EX:- In the case of MongoDB, the listening port is 27017 3. Host name of your database instance. EX:- Like localhost, in this case, it will be the Public DNS of your database instance 4. The password of your database if exists. First these details need to be set as environment variables in Elastic Be

How to import the Public Certificate of one WSO2 product to the trust store of another?

To demonstrate this point, I will use the 2 products WSO2 API Manager 2.1.0 (referred as APIM from here onwards) and WSO2 Enterprise Integrator 6.1.1 (referred as EI from here onwards). When using EI as the Business Process Server during configuration of Workflows in APIM, one step to perform is to import the public certificate of EI to the truststore of APIM [1]. So now let's see how this can be done. Step 1: Go to <EI_HOME>/repository/resources/security/ folder and execute the following keytool command. This command is used to export the public certificate of EI as a certificate file called wso2carbon.cer. Since the default keystore in EI is wso2carbon.jks, we have specified it as the keystore and the default alias is wso2carbon. Provide wso2carbon as the keystore password when prompted as it is the default password. After executing the above command from within the security folder in EI, you will see that a file with the name of wso2carbon.cer is created