How do malloc() and free() work?
Categories:
Understanding malloc() and free(): Dynamic Memory Management in C/C++

Explore the fundamental concepts of dynamic memory allocation and deallocation using malloc()
and free()
in C and C++. Learn how these functions interact with the heap and prevent memory leaks.
In C and C++, memory management is a critical aspect of programming. While local variables are allocated on the stack and global/static variables reside in the data segment, dynamic memory allocation allows programs to request memory at runtime from a region known as the heap. This flexibility is essential for handling data structures of unknown size, such as linked lists, trees, or arrays whose dimensions are determined during execution. The primary functions for dynamic memory management in C are malloc()
and free()
. C++ offers new
and delete
operators, but understanding malloc()
and free()
is fundamental, especially when working with C libraries or low-level programming.
How malloc() Works: Allocating Memory on the Heap
malloc()
(memory allocation) is a standard library function declared in <stdlib.h>
that allocates a specified number of bytes from the heap. It returns a void*
pointer to the beginning of the allocated block of memory. If the allocation fails (e.g., due to insufficient memory), malloc()
returns a NULL
pointer. It's crucial to always check the return value of malloc()
to ensure successful allocation.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n = 5;
// Allocate memory for 5 integers
arr = (int *) malloc(n * sizeof(int));
// Check if malloc was successful
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Initialize and print the allocated memory
printf("Allocated memory addresses and values:\n");
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
printf("arr[%d] = %d (Address: %p)\n", i, arr[i], (void *)&arr[i]);
}
// Don't forget to free the memory later!
free(arr);
arr = NULL; // Good practice to set pointer to NULL after freeing
return 0;
}
flowchart TD A[Program Calls malloc(size)] --> B{Heap Manager Checks Free Blocks} B -->|Enough Space| C[Finds Suitable Block] C --> D[Marks Block as Used] D --> E[Returns Pointer to Block (void*)] B -->|No Space| F[Returns NULL Pointer] E --> G[Program Casts Pointer and Uses Memory] F --> H[Program Handles Allocation Failure]
Flowchart of the malloc() memory allocation process.
void*
returned by malloc()
to the appropriate pointer type for type safety and correct pointer arithmetic. For example, (int *) malloc(...)
.How free() Works: Deallocating Memory and Preventing Leaks
free()
is the counterpart to malloc()
, also declared in <stdlib.h>
. It takes a pointer to a memory block previously allocated by malloc()
(or calloc()
, realloc()
) and returns that memory to the heap, making it available for future allocations. Failing to free()
allocated memory leads to memory leaks, where your program continuously consumes more memory than it needs, potentially leading to system instability or crashes over time.
#include <stdio.h>
#include <stdlib.h>
void allocate_and_free_example() {
int *data = (int *) malloc(10 * sizeof(int));
if (data == NULL) {
printf("Allocation failed in function.\n");
return;
}
printf("Memory allocated at %p\n", (void *)data);
// Use the memory...
data[0] = 100;
printf("data[0] = %d\n", data[0]);
free(data); // Deallocate the memory
data = NULL; // Set pointer to NULL after freeing
printf("Memory at %p freed.\n", (void *)data); // Will print NULL address
// Attempting to access freed memory is undefined behavior!
// printf("Attempting to access freed memory: %d\n", data[0]); // DANGER!
}
int main() {
allocate_and_free_example();
return 0;
}
flowchart TD A[Program Calls free(ptr)] --> B{Heap Manager Receives Pointer} B --> C{Marks Corresponding Block as Free} C --> D[Adds Block to List of Available Memory] D --> E[Memory is Now Available for Future malloc() Calls] E --> F[Program Continues Execution] subgraph Important Considerations G[Do NOT free() unallocated memory] H[Do NOT free() the same memory twice] I[Do NOT access memory after free()] end C --> G C --> H C --> I
Flowchart of the free() memory deallocation process.
free()
on the same pointer twice) or freeing memory not allocated by malloc()
(or calloc()
, realloc()
) leads to undefined behavior and can corrupt the heap, causing crashes or security vulnerabilities.Best Practices for Dynamic Memory Management
Effective use of malloc()
and free()
requires adherence to certain best practices to ensure program stability and prevent common memory-related bugs:
1. Always Check for NULL
After every malloc()
call, check if the returned pointer is NULL
. This indicates a failed allocation and allows your program to handle the error gracefully instead of crashing.
2. Pair malloc() with free()
For every successful malloc()
call, there must be a corresponding free()
call. This ensures that all dynamically allocated memory is returned to the system, preventing memory leaks.
3. Set Pointers to NULL After Freeing
After calling free(ptr)
, it's good practice to set ptr = NULL
. This prevents 'dangling pointers' and helps catch accidental attempts to use freed memory (though it doesn't prevent all such issues).
4. Avoid Double-Freeing
Ensure that you only free()
a memory block once. Setting pointers to NULL
after freeing helps, as free(NULL)
is a safe no-op.
5. Don't Access Freed Memory
Once memory is free()
d, its contents are undefined, and accessing it can lead to crashes or unpredictable behavior. This is known as a 'use-after-free' error.
6. Use calloc()
for Zero-Initialization
If you need allocated memory to be initialized to zero, use calloc()
instead of malloc()
. calloc()
takes the number of elements and the size of each element, and it guarantees that the allocated memory is zero-initialized.
7. Use realloc()
for Resizing
To change the size of an already allocated memory block, use realloc()
. It attempts to resize the block in place or allocates a new block, copies the data, and frees the old block.