Transcript for:
Understanding Dynamic Memory Allocation in C

In our previous lesson, we learnt the concept of dynamic memory allocation, we learnt about stack and heap in the context of application's memory. In this lesson we will be looking at various library functions available in C for dynamic memory allocation. We will look through these functions in some code examples. We will be talking about three functions that allocate block of memory on the heap and these functions are malloc, calloc and realloc. And, we will also be talking about one functions that deallocates a block of memory on the heap and this function is free. Let us first talk about malloc. This is the most frequently used library function for dynamic memory allocation and the signature or the definition of this function is something like this. this function as argument asks you for theTsize of the memory block in bytes. This data type size_t , if you are not aware of it stores only positive integer values. So, you can think of this particular data type as unsigned integer data type. The size cannot be a negative value. It can either be a zero or a positive value. And, to force this kind of behavior we use this particular type. And this function malloc returns a void pointer. We have talked about void pointers in our previous lessons. Malloc returns a void pointer that gives us the address of the first byte in this block, the block of memory that it allocates. So, using malloc you kind of say that hey, give me a block of memory with these many bytes. Let us say what we have here in the right is the heap section of memory available to us. Each of these partitions are 1 byte. As we know each byte in the memory has an address. Let us say this bottom most byte has the address 200 and the next byte is 201 and we go on increasing till the top. I will mark the address of some of these bytes. Let's say in our code we make a call to malloc something like this, that hey give me a block of memory that is 4 bytes, let's say we store the address returned by malloc in a void pointer variable. Now let's assume that this particular rectangular block we are showing here in red, is what get's allocated against this call to malloc. These are 4 bytes and the starting address is 208. So, what will be the address in p . If we want to print the address in p, then what will be the output. The output will be 208. This is cool. Using malloc we are able to allocate some amount of memory, reserve some amount of memory. But, why do we reserve memory? We reserve or allocate memory because we want to store some data there. So we do not want to just randomly allocate some bytes. So, although it is correct to write a statement like this. In practice, we would not do so. In practice, we would first calculate how much amount of memory we need to store our data. So, let's say we want to store an integer. Just one integer. So, I need memory block equal to the size of one integer. We typically use the function sizeof which returns us the size of the variable in bytes. To figure out the size of the data type, and then we typically multiply this particular value returned by sizeof by the number of units that we need. So, if we need just one integer, if we need memory for just one integer, this is good. If we need memory for 10 integers, we would write something like this: give me 10 X sizeof integer. Integer is a primitive data type. Sometimes, you may know that, i know that size of integer is 4 bytes. So, i can write something like 104 here, but its not a good practice to do so. Size of a variable depends upon the compiler and we would also be using malloc to allocate memory for complex data types. So, we must use malloc like this. Total number of bytes should be written as number of elements size of one unit. One unit of the data. Lets say we want to have space for 3 integers. I have picked up 3 so I that have enough space to allocate memory in the figure that I am showing here. let's say this block of 12 bytes is allocated because size of integer is 4 bytes typically. So, the starting address would be 201. Now, how do we fill in data here. If you see malloc returns a void pointer and void pointer as such cannot be de-referenced. You cannot write a statement something like *p = 2, this is incorrect, you cannot dereference a void pointer. A void pointer in fact is only used as a generic pointer type which is normally typecasted into a pointer type of a particular data data type. And then it is used. Because malloc itself is a generic function to allocate some number of bytes in memory, in heap, it does not care whether you are allocating this memory to store character or to store integer or to store any other data type. it simply returns a void pointer to the starting address. To be able to use this block of memory, we first need to typecast this void pointer into a pointer of some data type. So here, we will write something like int *p instead of picking up a void pointer, we pick up a pointer to integer because we want to operate with integers. So, to dereference we need an integer pointer type. So, we do the typecasting here of the void pointer and now this statement is valid. If we want to assign some value to the second element , second integer in this list, then we will do something like we will dereference that address p+1, lets say, we write the value 4 here and if we want to access the third integer, then we will write something like this. let's say the value 6 here. By asking the memory block for 3 integers, we are basically creating an array of integers with 3 elements. We could also write this asterisk p as P[0] and we could also write (p+1) as P[1] and similarly (p+2) as P[2]. They mean the same. All the manipulation on dynamically allocated memory happens through pointers. You have a pointer to the starting address and then if you want to go to the next element in the array, you increment the pointer and this is how things happen. We have two more functions that allocate block of memory. Let us now talk about calloc. The signature of definition of calloc is something like this. calloc also does the same stuff as malloc, it is only slightly different. callod also returns a void pointer but calloc takes 2 arguments instead of 1 argument, it takes 2 arguments. The first argument is the number of elements of a particular data type and the second argument is the size of the data type. So, with malloc, if we have to declare an array of size 3, an integer array of size 3, we would say 3sizeof(int). With calloc, we would say something like this. The first argument is how many units of the data type you want or the number of elements and the second argument is the size of data type in bytes. There is one more difference between malloc and calloc. When malloc allocates some amount of memory, it does not initialize the bytes with any value, so if you do not fill in any value into these addresses allocated by malloc, you would have some garbage there. But if you allocate memory through calloc, calloc sets all byte positions with value zero. So, it also initializes the memory that it allocates to zero. The third function that we want to talk about is realloc. If you have a block of memory, dynamically allocated block of memory and if you want to change the size of this block of memory, then you can use realloc. The definition or the signature of this particular function is , this function takes two arguments. The first argument is pointer to the starting address of the existing block and the second argument is the size of the new block. There can be a couple of cases in realloc, the size of the new block that we want may be larger than the size of the previous block. In that case, machine may crate an entirely new block and copy the previous data whatever bytes that was written in previous block into the new block. If contiguous or consecutive memory is already available with the existing block, the existing block may also be extended. Let us now look at some code examples and see what we can do with these 3 functions and we will discuss the function free in our code itself. I'll write some C code and look at some of the use cases in which we can use dynamic memory allocation. The first use case is let us say , we want to declare an array and we want to first ask the user, the size of the array and then we want to declare an array exactly of this particular size entered by the user. So, lets say I have a variable n and I write a print statement like enter size of array and then I input this number n from the console. Now, I want to declare an array of size n only. So, can i do something like this? Well, No ! This particular value in the braces cannot be a variable, this will give you compilation error. We need to know the size of the array. We cannot know the size of the array during run time. In such a scenario, we can allocate the memory dynamically. So, we will write something like int A is equal to and we will make a call to malloc to allocate a memory block equal to the size of n integers and this will again give a compilation error unless we typecast this particular return of malloc to integer pointer and now we have an array of size n. And we can fill in some data into these dynamically, into the elements of this dynamically allocated array. Lets say we want to put data something like first element is 1 and the second element is 2 and so on. So, we will write something like A[i] = i+1. We can print the elements in the array. lets say we want to pick up size of array as 10. Ok, so the output is as expected. We have 10 elements from 1 to 10. If I give size of array as let's say 25, then this is what we get. We get all the elements till 25. if we wanted to use calloc instead of malloc here, the only change in this code would be that we would use calloc here and we would have 2 arguments - n would separate out as first argument and second argument will be size of int and this program will run seamlessly. There is one more difference between malloc and calloc, if we do not use this initialization, then with calloc, as you can see, all the elements are being printed as zero. They are all initialized to zero. But if we were using malloc here, then these elements are not initialized, there is some garbage value at these of these indices in the array. So, this is one difference between malloc and calloc. calloc initializes, fills the value zero into each byte while malloc doesn't do this initialization. I'll rewrite this initialization loop again and now, we will talk about free. Any memory that is dynamically allocated remains allocated till the lifetime of the program, till the time the program is executing unless you explicitly de-allocate it and to de-allocate memory allocated using malloc and calloc and realloc, we have the function free and to the function free, you pass starting address of the memory block as argument. Now, what will happen if I will free A. if we free A, then the data from that memory is erased. it may not or may not be erased, it actually depends upon your compiler or machine, but that memory will be available for allocation against another call to malloc. Lets see what happens in this case when we are printing after freeing that particular memory block. I give array size as 5 and as you can see, the elements being printed here, there is some garbage value being printed. if this free was not there, we would have printed elements 1,2,3,4,5 as initialized. Not the obvious question would be even though we are freeing the memory here, we are able to access that , the value at that particular memory location using this statement, when we are using A[i], the element at index i. Well, that is one dangerous thing about pointers. if you know the address, you can look at the value at that address, but you should read and write to that address only if that address is allocated to you. What if that address is not allocated to you like in this case. Well, you do not know what you are reading or what you are writing or what behavior it will have. It actually depends upon the compiler and the machine. In my case, lets say after freeing, we try to access the third element A[2] and try to push some value there, lets see what happens if you run this program. Lets give size of array as 5 again. If you see, even after free, we are able to modify the value at this particular address - A[2], but on some machines, such a program may cause our program to crash. We should always be sure to use the memory that is allocated, otherwise its like shooting in the dark, we do not know what will happen. We will now talk about realloc. If we want to modify the size of a memory block, lets say we have a memory block to store n elements in an array and we want to extend it to, may be we want to double the size of array or may be we want to reduce the size of the array to half. For such scenario, we use realloc and call to realloc will be something like this. Lets say, we take another pointer variable B, then to realloc, we pass the previous pointer A and size of the new block so the new block is 2nsizeof(int) and we will of course do the typecasting here. Ok, so now what this call will do is it will create a new memory block of size 2n and copy the values in the previous block, in the previous memory block A into this new memory block. How realloc works is that if the size of the new block is greater than the size of the previous block, the if it is possible to extend the previous block, find some consecutive memory with the same block, then the previous block itself is extended. Else, a new block is allocated and the previous block, the content from the previous block is copied and the previous block is de-allocated. This will become further clear if I will write this print statement. I'll print the previous block address stored in A and new address stored in B and I'll also print all the 2n elements in B now. And I'll print each element in the array B in one single line. Let's say size of the array is 5 again. Ok, so the previous block address is 9 9 2 0 1 2 8, and if you can see the previous address was also the same, so it was possible to extend the previous block only and in B, the first five elements are from A and rest 5 elements are garbage value. If we wanted to reduce the array size to lets say half, then the same block, the previous block itself will be reduced. So, lets say I want to print the previous block now, the n elements. Now as you can see, the first two elements are copied - 1 and 2, they are not copied in fact, they are there already. The rest 3 are de-allocated. When we divide by 2, we take only the integral part, so we kind of deallocate this space for 3 elements here. In fact if we give the size to be zero here, all that will happen is that the complete block for A will be deallocated. So, this statement will be equivalent to using free upon A. In most cases, we will put the return address by realloc into the same integer pointer. So, you can write, instead of writing B here, we can write A also. We can also pass the first argument to realloc as NULL. if the first argument is NULL and the second argument is size, and lets say we want to create something like a new block with address, starting address stored in B, then if the first argument is NULL, then this is equivalent to calling a malloc. This only crates a new block, does not copy anything from the previous block. So, realloc can be used with the right arguments as substitute for free as well as substitute for malloc. This was all about malloc, calloc and free. We will see more code on dynamic memory allocation in the coming lessons. So, Thanks for watching !