Welcome to your next lesson in our "Programming in C" module. In the previous session, we established the deep connection between pointers and arrays, understanding how to use pointer arithmetic to access and manipulate elements in memory blocks allocated at compile time. A key takeaway was that an array's name often acts as a pointer to its first element, allowing for powerful and flexible data access.
However, this approach has a significant limitation: the size of arrays like int arr[10]; must be known when you write the code. What if you need to store a number of elements that is only determined when the program is running—for instance, based on user input?
This lesson addresses that exact problem by introducing dynamic memory management. You will learn how to allocate and deallocate memory on-the-fly from a special memory region called the heap. Our goal is to master the standard library functions—malloc, calloc, realloc, and free—and, crucially, to identify and avoid the common pitfalls associated with them: memory leaks and dangling pointers. For the GATE exam, questions involving these pitfalls are common and test a fundamental understanding of program execution and memory safety.
The Heap and Dynamic Allocation Toolkit
So far, the variables and arrays we've worked with have been created on the stack (local variables in functions) or in static/global data segments. Stack memory is managed automatically; it's allocated when a function is called and deallocated when the function returns. Dynamic memory management operates differently, using a large pool of memory called the heap. You, the programmer, are in complete control of requesting blocks from the heap and must explicitly return them when you're done.
The following video from mycodeschool provides a comprehensive overview of the four essential functions for this task.
Dynamic memory allocation in C - malloc calloc realloc free
This video introduces the core concepts and functions of dynamic memory allocation in C. Pay close attention to the syntax, return values, and specific behaviors of each function.
First, watch the introduction to understand why we need dynamic memory, contrasting it with static and automatic allocation. This is covered from the beginning. Next, learn about malloc (memory allocation). Focus on its syntax, its void* return type which requires casting, and the importance of checking for a NULL return value, which indicates allocation failure. This is detailed from this segment. The video then introduces free. Understand that every malloc must have a corresponding free to return the memory to the heap. This is explained from this part. Now, look at calloc (contiguous allocation). Note its two key differences from malloc: it takes the number of elements and the size of each element as separate arguments, and it initializes the allocated memory to zero. Watch this from this section. Finally, explore realloc (re-allocation), which is used to resize a previously allocated memory block. Understand its behavior when shrinking or expanding memory, especially the possibility that it might move the entire block to a new location. This is covered from the end.
Let's consolidate the key points. All these functions are declared in the <stdlib.h> header.
-
void* malloc(size_t size): Allocates a single block ofsizebytes. The memory is not initialized; it contains garbage values. It returns avoid*pointer to the block, which you must cast to the appropriate pointer type. If it fails, it returnsNULL.- Usage:
int *p = (int*) malloc(10 * sizeof(int));
- Usage:
-
void* calloc(size_t num, size_t size): Allocates memory for an array ofnumelements, each ofsizebytes. The memory is initialized to zero.- Usage:
int *p = (int*) calloc(10, sizeof(int));
- Usage:
-
void* realloc(void* ptr, size_t new_size): Changes the size of the memory block pointed to byptrtonew_size.- If
new_size> old size, the extra memory is not initialized. - If
new_size< old size, the data at the end is truncated. - The function may move the memory block to a new location if it cannot be expanded in place. It copies the old data to the new location. If it moves the block, the original pointer
ptrbecomes invalid. - If it fails, it returns
NULL, and the original block atptris not freed.
- If
-
void free(void* ptr): Deallocates the memory block pointed to byptr. The pointerptrmust be one that was returned bymalloc,calloc, orrealloc.
The Peril of Memory Leaks
With the power of dynamic allocation comes the responsibility of manual memory management. Forgetting to deallocate memory that is no longer needed leads to a serious problem known as a memory leak.
A memory leak occurs when a program allocates memory on the heap but loses all pointers to that memory, making it impossible to free(). This "orphaned" memory remains consumed by your program but is unusable, causing the program's memory footprint to grow over time. In long-running applications like servers, this can eventually exhaust all available system memory and crash the system.
The next video provides an excellent practical demonstration of a memory leak and how to fix it.
This video from mycodeschool uses a simple betting game to illustrate what a memory leak is, why it happens, and how to prevent it.
Start by watching the introduction, which recaps the memory layout (stack vs. heap) and defines a memory leak. Next, observe the game's execution when the card array is a local variable on the stack. Notice how the program's memory consumption remains stable, as explained from this segment. Now, see what happens when the code is changed to allocate the array on the heap using malloc but without calling free. Watch how the memory usage steadily increases as the game is played, from this part. This is the memory leak in action. The video then provides a visual explanation of why this happens. When the function returns, the local pointer variable on the stack is destroyed, but the memory block on the heap persists, now without any pointer to it. Finally, see the simple fix: adding a call to free() at the end of the function. Observe how this stabilizes the memory consumption, as shown from this final section.
In GATE questions, memory leaks are often created in subtle ways:
- A pointer is overwritten with a new address from
mallocbefore the old memory it was pointing to is freed. - A local pointer inside a function allocates memory, but the function returns without returning the pointer, orphaning the memory block.
The Danger of Dangling Pointers
The flip side of a memory leak is a dangling pointer. This is a pointer that continues to point to a memory location that has already been deallocated by a call to free() or realloc().
Using a dangling pointer leads to undefined behavior, one of the most treacherous types of bugs in C. The program might crash, or it might appear to work correctly sometimes but fail unpredictably. This happens because the memory manager might have already allocated that same piece of memory for a different purpose. Writing through a dangling pointer can corrupt unrelated data, leading to bugs that are extremely difficult to trace.
Here’s a classic example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*) malloc(sizeof(int));
*p = 123;
printf("Address: %p, Value: %d\n", p, *p); // Works fine
free(p); // The memory is deallocated. p is now a dangling pointer.
// The line below is UNDEFINED BEHAVIOR.
// It might print 123, print garbage, or crash the program.
printf("Accessing freed memory. Value: %d\n", *p);
*p = 456; // Even worse! Corrupting memory that may now belong to something else.
return 0;
}
Best Practice: To mitigate the risk of dangling pointers, it's a good habit to set a pointer to NULL immediately after freeing it.
free(p);
p = NULL; // Now p is not dangling.
If you later accidentally try to dereference a NULL pointer, your program will almost certainly crash immediately with a clear error (like a "segmentation fault"), which is far easier to debug than silent data corruption.
Dangling pointers can also be created by realloc. If realloc moves a memory block to a new location, any other pointers that were holding the old address become dangling.
Conclusion
In this lesson, we transitioned from accessing pre-allocated memory to managing it dynamically. This skill is essential for writing sophisticated programs where data requirements are not fixed at compile time.
Key Takeaways:
- Dynamic memory is allocated from the heap using
malloc,calloc, andrealloc. mallocallocates uninitialized memory, whilecallocallocates zero-initialized memory.reallocresizes memory blocks and may move them.- It is the programmer's absolute responsibility to release memory back to the heap using
free(). - A memory leak occurs when allocated heap memory becomes unreachable (orphaned) because no pointers refer to it anymore.
- A dangling pointer points to memory that has already been freed, and using it results in undefined behavior. Setting freed pointers to
NULLis a valuable defensive practice.
You now have the tools to create arrays, strings, and other data structures of any size at runtime. Our next lesson, "Arrays & Strings," will build directly on this foundation. We will explore the standard C library functions for string manipulation (like strlen, strcpy, strcmp) and see how they operate on both statically and dynamically allocated character arrays.