Pointers and Dynamic Memory Allocation in C and C++

In this tutorial, we will discuss the concept of dynamic memory allocation in C/C++. Memory is one of the major resource on our modern computing system, especially RAM. Because programs under execution store in RAM and it is a limited resource. By using dynamic memory allocation, we can efficiently allocate memory for a program on run-time. By the end of this tutorial, you will be able to know how to dynamically allocate memory using malloc(), realloc(), and calloc() functions available in C programming.

Firstly, we will see the memory layout of a C memory, the way the operating system stores a program in memory, and what are different memory segments of a program.

C Program Memory Layout

The memory layout of a program can be divided into four segments. One segment of the memory is assigned to store the instructions that need to be executed. Another section stores all the static or global variables that are not declared inside a function. The next section of the memory stores all the information of function calls and all the local variables. This is known as the ‘Stack.’ The local variables are declared inside a function. Their lifetime is only till the function is executing.

We have previously looked upon the concept of Stack in the programs memory in the following article as well: Pointers as Function Arguments or call by reference in C

The fourth segment is known as the ‘Heap.’

Pointers to function arguments Application memory

The memory set aside for these three segments: code segment, global variable segment and the stack is defined during program compilation but we can allocate memory on the heap segment during program execution.

STACK

We will first look at how these three segments are used when a program executes. Let us have a look at a simple C program.

#include <stdio.h>
int output;
int square(int x)
{
  return x*x;
}

int SQUAREofSUM(int x, int y)
{
    int z = square(x+y);
    return z;
}

int main()
{
    int num1 = 3, num2 = 2;
    output = SQUAREofSUM(num1,num2);
    printf("\nFirst Number: %d",num1);
    printf("\nSecond Number: %d",num2);
    printf("\nSquare of Sum of Numbers: %d", output);
}

We have a function square() that gives us the square of a number. We have another function SQUAREofSUM() that takes in two integer arguments ‘x’ and ‘y’. It returns us the square of (x+y). In the main() method we are calling the SQUAREofSUM() method and passing it two arguments ‘num1’ and ‘num2’.

Code Output

Now let’s see the code output. After the compilation of the above code, you will get this output.

Pointers and dynamic memory pic7

Stack Memory

Let us now see what happens in the memory when this program is executed. Below you can view the section of the application’s memory showing the Stack segment and the global variable section.

Pointers and dynamic memory pic1

When the program starts executing, firstly the main() method is invoked .When the main() method is invoked some amount of memory from the stack is allocated for the execution of the main(). The amount of memory allocated on the stack for execution of main() can also be called the stack frame for the main(). All the local variables, arguments and the information where this function should return back to, is stored within the stack frame. The size of the stack frame for a method is calculated when the program is compiling.

The ‘output’ variable is a global variable so it is found in the global variable segment.

Pointers and dynamic memory pic2

When main() calls SQUAREofSUM() method then a stack frame is allocated for the call to SQUAREofSUM(). All these local variables (x, y and z) will be found in this particular stack frame.

Pointers and dynamic memory pic3

The SUMofSQUARE() then calls the square() function so another stack frame for square() will be created with its own local variables. At any time during the execution of the program, the function at the top of the stack executes and the rest are at a pause, waiting for the above function to return something, and then it will resume execution.

Pointers and dynamic memory pic4

The ‘output’ variable is a global variable as it is not declared inside a function so it is found in the global variable segment. We can access this variable anywhere. In this particular statement, we call the SQUAREofSUM() function.

output = SQUAREofSUM(num1,num2);

The SQUAREofSUM() function in return calls the square() function in the following statement.

 int z = square(x+y);

Our call stack consists of the three methods as shown above. As soon as the square() function will return, it will be cleared from the stack memory. So now the SQUAREofSUM() function will resume.

Pointers and dynamic memory pic5

Once again when SQUAREofSUM() finishes, the control will come to this particular line:

  output = SQUAREofSUM(num1,num2);

The main() method will resume again. The printf() function will be called and therefore it goes to the top of the stack.

Pointers and dynamic memory pic6

After printf() will finish, the control will go back to the main() method. This will cause the main() to finish executing. As the main() will finish, the program will also complete. In the end, the global variables will also get removed.

Pointers and dynamic memory pic1

Note: We should assign a variable as global only if it is needed multiple places in multiple functions and is also required for the whole lifetime of the program. Otherwise, it is a waste of memory to keep a variable for the whole lifetime of the program execution.

Limitations of Stack

When our program starts, the operating system allocates some amount of reserved space for the stack e.g. 1MB. The actual allocation for the stack frame and for the local variables happens from the stack during runtime. If our call stack grows beyond the reserved memory for the stack for e.g. if method A() calls method B(). B() in return calls C() and we go on calling different functions this will exhaust the whole space reserved for the stack. This is known as stack overflow. In the case of stack over flow, our program crashes. This can usually happen if there is an issue in the code for recursion where it runs indefinitely.

The memory set aside for stack does not grow during runtime. The application can not request for more memory for the stack. So for e.g. if the memory allocated for the stack frame is 1MB then if the allocation of the variables and functions in the stack exceeds 1MB then the program will crash. Furthermore, the allocation/deallocation of memory onto the stack happens by a set rule. When a function is called, it is pushed onto the stack, when it finishes, it removed from the stack. It is not possible to manipulate the scope of a variable if it is on the stack.

Another limitation is that if we need to declare a larger data type for e.g. an array as a local variable then we need to know the size of the array at the time of compilation. If we have to decide how large the array will be based on some parameter during runtime then it is a problem with the stack. For all these problems like allocating large amount of memory or keeping variables in the memory till the time we want, we have a HEAP.

Note: Stack is an implementation of stack data structure but heap is not an implementation of the heap data structure.

HEAP Memory Segment

Unlike the stack, the application’s heap is not fixed. Its size can vary. During the lifetime of the application, there is no set rule for the allocation or deallocation of memory. The user can control how much memory to use from the heap and till what time to keep the data in the memory during the application’s lifetime. Heap can grow as long as you do not run out of the memory on the system itself. Heap is also known as ‘free pool of memory’ or ‘free store of memory.’ The implementation of heap by the operating system, language runtime or the compiler depends upon the individual system and can vary from system to system.

An abstracted way of looking at the heap as a user is that it is a large free pool of memory available to us that we can use according to our needs. Heap is also referred to as ‘dynamic memory.’

Dynamic Memory Allocation

Common Functions for Allocating/Deallocating Memory

To use dynamic memory in C programming, we require four major functions. These are known by the following names:

  1. malloc
  2. calloc
  3. realloc
  4. free

The first three functions are used to allocate memory and the last one is for deallocating memory on the heap.

  • malloc is the most frequently used library function for dynamic memory allocation. The definition of this function is as follows:
void* malloc(size_t size)

This function takes in the size of the memory block in bytes as an argument. The data type size_t stores only zero or positive integer values. It is an unsigned integer data type. This function returns a void pointer that gives us the address of the first byte of the block of memory it allocates.

  • calloc function is slightly different from malloc. The definition for calloc is as follows:
void* calloc(size_t num, size_t size)

calloc also returns a void pointer but takes in two arguments instead of one. The first argument is the number of elements of a particular data type. The second argument is the size of the data type. So, with malloc if we have to declare an array for e.g. an integer array of size 5, we would say malloc(5*sizeof(int)) but with calloc we would say calloc(5, sizeof(int)). Here, the first argument is the number of units of the data type you want and the second argument is the size of the data type in bytes. There is one more difference between malloc and alloc. When malloc allocates some amount of memory, it does not initialize the bytes with any value. Hence, garbage values will be found instead. Whereas if you allocate memory through calloc, then it sets all the byte positions with the value zero.

  • realloc is used if we have a dynamically allocated block of memory and we want to change the size of that block of memory. The definition of realloc is as follows:
void* realloc(void* ptr, size_t size)

The realloc function takes in two arguments. The first argument is the pointer to the starting address of the existing block and the second argument is the size of the new block. If we want the size to be larger than the previous one, then the system may create a new block and copy the previous data that was written there into the new block. If contiguous memory is already available with the existing block then the existing block may be extended.

You can also use these four functions in C++ programming as well but mostly the following two operators are used instead. To use dynamic memory in C++ programming, we require two operators. These are:

  1. new
  2. delete

Example C Code: Allocate Dynamic Memory for integers

Let us look at an example C program to understand the concept of heap in a better way.


#include <stdio.h>
#include <stdlib.h>

int main(){
    int x;
    int *ptr;
    ptr= (int*)malloc(sizeof(int));
   *ptr = 50;
    ptr= (int*)malloc(sizeof(int));
   *ptr = 150;
}

The integer variable ‘x’ is declared in the main() method hence this is a local variable. It is placed in the stack. Memory for this particular variable ‘x’ will be allocated from the stack frame of the main() method.

Pointers and dynamic memory pic8

Malloc() Integer

If we want to store an integer on the heap we have to first reserve some space on the heap. To reserve some space allocated on the heap, we need to call the malloc function, as shown in the following line:

int *ptr;
ptr = (int*)malloc(sizeof(int));

The malloc function asks for how much memory to allocate on the heap in bytes. This is determined by the sizeof() operator according to its operands data type.

For this example, our operand is an integer that takes up 4 bytes of memory. Hence, one block of 4 bytes will be reserved on the heap. The malloc will return a starting address of this particular block. Moreover, malloc returns a void pointer. If for example the starting address of the 4 bytes block is 300, then the malloc will return us 300. This way we have a pointer to an integer ‘ptr’ that is a local variable to the main(). So, ‘ptr’ will be allocated in the stack frame of the main() method.

In this particular statement we have performed typecasting. This is because malloc returns a void pointer and ‘ptr’ is an integer pointer. ‘ptr’ stores the address of this block of memory which was 300 in our case. In this way we have got a block of memory on the heap that we want to use to store an integer.

Pointers and dynamic memory pic9

To store a value in this memory block we will use the following statement:

*ptr = 50; 

Here we are storing the value ’50’ in this memory block. We have derefrenced the location using the pointer ptr and then set it equal to ’50.’

Pointers and dynamic memory pic10

The only way to use memory on the heap is through reference.

The function of the malloc is to look for free space on the heap, reserve it and return us the pointer. The only way this memory block can be accessed is through a pointer variable that will be local to your function.

Now, in the next lines of code we are calling the malloc function again.

ptr = (int*)malloc(sizeof(int));
*ptr = 150;

This way another block of 4 bytes will get reserved in the heap. For example purposes let us assume its starting address is 100. Now, the address that is returned by the second call to malloc will be stored in the variable ‘ptr.’ Thus, ‘ptr’ is now pointing to address 100 instead. We are also storing the value ‘150’ in this memory block.

Pointers and Dynamic_Memory pic11

What we did was we allocated one more block of memory in the heap and we modified the address in ‘ptr’ to point to this particular block instead. Notice that the previous block will remain in the heap. The memory consumed will thus not be cleared off automatically.

Freeing Dynamically Allocated Memory

At any point in the program if we have used some block of memory that was dynamically allocated using malloc, we also need to clear it as it is not in use anymore. Let us modify the C program code a bit by adding free() operator in between the two memory allocations in order to remove the first memory block.

#include <stdio.h>
#include <stdlib.h>

int main(){
    int x;
    int *ptr;
    ptr= (int*)malloc(sizeof(int));
   *ptr = 50;
    free(ptr);
    ptr= (int*)malloc(sizeof(int));
   *ptr = 150;
}

After we are done using the memory block at starting address 300, we have made a call to the function free(). Any memory which is allocated using malloc is automatically cleared off by calling free(). We will pass the pointer to the starting address of the memory as the parameter of free().

free(ptr);

Now, the first block of memory will be cleared.

Pointers and dynamic memory pic12

In terms of the scope of the variable, unlike stack, anything allocated on the heap is not automatically deallocated when a function finishes. We can control when to free anything on the heap.

Example C Code: Dynamically Allocate Memory for Arrays

#include <stdio.h>
#include <stdlib.h>

int main(){
    int x;
    int *ptr;
    ptr= (int*)malloc(sizeof(int));
    *ptr = 50;
    ptr= (int*)malloc(10*sizeof(int));
}

If we want to store an array on the heap for example an integer array then make a call to malloc asking for one block of memory equal to the total size of the array in bytes. Supposing it is an integer array of 10 elements then we will make a call to malloc asking (10 x size of integer: 4)= # of bytes. Now one large contiguous bock of memory for 10 integers will be allocated on the heap. We will get the starting address of this block which will be the base address of the array. ‘ptr’ will now point to the base address of this block as shown in the section of the system’s memory.

Pointers and dynamic memory pic13

In our code, we can use these 10 integers as ptr[0], ptr[1], ptr[2] and so on. As we know, ptr[0] is the value at address ptr and ptr[1] is the same as value at address (ptr+1).

If malloc is not able to find any free block of memory then it returns null. For error handling we need to know this.

Modifying Example Code for C++

Now, if we want to write the same example code as mentioned above but in C++, instead of using malloc and free we will use free and delete operators instead. Just modify the code as follows:

#include <stdio.h>
#include <stdlib.h>

int main(){
    int x;
    int *ptr;
    ptr= new int;
    *ptr = 50;
    delete ptr;
ptr = new int[10];
delete[] ptr;
}

As you may notice, instead of using malloc we are using new operator. Likewise, instead of using free we are using delete operator.

If we want to allocate an integer array of size 10 in C++, we will use the following statement:

ptr = new int[10];

To free an array, we will use the delete operator with a square bracket:

delete[] ptr;

In C++ programming, we do not need to do any type of typecasting like we used to in the case of C programs. Malloc returns void so we have to typecast it back to an integer pointer. New and delete operators are type safe. This means that they are used with a type and return pointers to a particular type only. Thus, here ‘ptr’ is getting a pointer to integer only.

Example Code 2

Not let us look at another example code in C programming where the user will be asked for the size of the array.

#include <stdio.h>
#include <stdlib.h>

int main(){
int n;
printf("Enter the size of the array\n");
scanf("%d",&n);
int *array = (int*)malloc(n*sizeof(int));
for(int i=0;i<n;i++)
{
    array[i] = i*2;
    
}
for(int i=0;i<n;i++)
{
    printf("%d ",array[i]);
}
}

We will declare an array of the particular size entered by the user. We can not know the size of the array at runtime thus we will allocate the memory dynamically. This is done in the following line:

int *array = (int*)malloc(n*sizeof(int));

We will make a call to the malloc function to allocate a memory block equal to the size of ‘n’ integers. Remember to typecast the return of malloc in this case to integer pointer as well otherwise a compilation error will occur. We have now created a dynamically allocated array of size ‘n.’

Using the for() loop we will store the values in our array dynamically. Here we are storing even values in the array like 0,2,4,6…

for(int i=0;i<n;i++)
{
    array[i] = i*2;
    
}

After that we are using another for() loop to print the array.

Now let’s see the code output. After the compilation of the above code, you will get the option to enter the size of the array. We have set the size to ‘5.’ Thus, the values in the array will be 0,2,4,6 and 8 respectively.

Pointers and dynamic memory pic14

Similarly, we we set the size to 10 then the output will be as follows:

Pointers and Dynamic_Memory pic15

Dynamically Allocate Memory using Calloc()

Instead of using the malloc function, let us use the calloc function in the example code above instead.

#include <stdio.h>
#include <stdlib.h>

int main(){
int n;
printf("Enter the size of the array\n");
scanf("%d",&n);
int *array = (int*)calloc(n,sizeof(int));
for(int i=0;i<n;i++)
{
    array[i] = i*2;
    
}
for(int i=0;i<n;i++)
{
    printf("%d ",array[i]);
}
}

For the calloc function we will have two arguments instead of one. ‘n’ will be the first argument and size of integer will be the second argument.

Thus, we will be only changing one line in the code which is shown below:

int *array = (int*)calloc(n,sizeof(int));

Rest of the program remain the same.

Dynamically Allocate Memory using Calloc()

Let us now include realloc as well in our program code. For example, we want to modify the size of our memory block associated with the array we created dynamically, we will call the realloc() function.

#include <stdio.h>
#include <stdlib.h>

int main(){
int n;
printf("Enter the size of the array\n");
scanf("%d",&n);
int *array = (int*)malloc(n*sizeof(int));
for(int i=0;i<n;i++)
{
    array[i] = i*2;
    
}
int *ptr = (int*)realloc(array,3*n*sizeof(int));
printf("Previous memory block address: %d, New memory block address: %d\n",array,ptr);
for(int i=0;i<n;i++)
{
    printf("%d ",ptr[i]);
}
}

We will create another pointer variable called ‘ptr’ and set it equal to the call to realloc(). The realloc() function will take in the previous pointer which was ‘array’ in our case as the first argument. The second argument will be the size of the new block. We want the size of the new block to be 3 times that of the previous one. Do not forget to perform the typecasting as well.

int *ptr = (int*)realloc(array,3*n*sizeof(int));

This call will create a new memory block of size 3n and copy the values from the previous memory block ‘array’ to this new memory block ‘ptr.’

If the size of the new block is greater than the size of the previous memory block then it is possible to extend the previous block. Otherwise, a new block of memory is allocated and the previous is deallocated after the values from that block have been copied.

Now let’s see the code output. After the compilation of the above code, you will get the option to enter the size of the array. We have set the size to ‘5.’ Thus, the values in the array will be 0,2,4,6 and 8 respectively. You may notice that the address of the previous memory block is same as that of the new memory block. This means that the previous memory block was extended.

Pointers and Dynamic_Memory pic16

Now if we want to change the size of the new block to half that of the previous one we can modify the statement as follows:

int *ptr = (int*)realloc(array,(n/2)*sizeof(int));

This way the previous memory block will be reduced in size.

Leave a Comment