C Dynamic Memory Allocation - Complete Guide
Master C dynamic memory allocation including static vs dynamic memory, malloc, calloc, realloc, free functions, memory management techniques, and programming best practices.
Static vs Dynamic
Memory comparison
4 Functions
malloc, calloc, realloc, free
Memory Errors
Leaks, dangling pointers
Best Practices
Safe memory management
Introduction to Dynamic Memory Allocation
Dynamic memory allocation allows programs to request memory at runtime rather than compile time. This enables flexible data structures that can grow or shrink as needed, efficient memory usage, and handling of data whose size is unknown at compile time.
Why Dynamic Memory Matters
- Runtime Flexibility: Allocate memory as needed during execution
- Efficient Memory Usage: Use only what you need
- Data Structures: Essential for linked lists, trees, graphs
- Unknown Size: Handle data of unpredictable size
- Large Data: Allocate memory beyond stack limits
- Shared Memory: Create memory that persists beyond function calls
Memory Allocation Functions
- malloc(): Allocate raw memory block
- calloc(): Allocate and zero-initialize
- realloc(): Resize existing allocation
- free(): Release allocated memory
- Memory Leak: Allocated memory not freed
- Dangling Pointer: Pointer to freed memory
Core Concepts of Dynamic Memory
Dynamic memory is allocated from the heap segment, managed manually by the programmer. Every malloc/calloc must have a corresponding free. The heap grows upward, while the stack grows downward. Understanding this memory model is crucial for preventing memory errors and writing efficient programs.
Static vs Dynamic Memory Comparison
Here is a comprehensive comparison of static and dynamic memory allocation in C:
| Feature | Static Memory Allocation | Dynamic Memory Allocation |
|---|---|---|
| Allocation Time | Compile time | Runtime |
| Memory Segment | Stack/Data segment | Heap segment |
| Size Determination | Fixed at compile time | Determined at runtime |
| Lifetime | Automatic (function scope) or Program duration | Until explicitly freed |
| Management | Automatic by compiler | Manual by programmer |
| Speed | Faster (compile-time) | Slower (runtime overhead) |
| Flexibility | Fixed size | Can grow/shrink |
| Risk | Stack overflow if too large | Memory leaks if not freed |
| Usage | Small, known-size data | Large, variable-size data |
| Example | int arr[100]; |
int *arr = malloc(100*sizeof(int)); |
Memory Segments Visualization:
Code Segment
Program instructionsData Segment
Static/Global variablesHeap Segment
Dynamic memory (grows upward)Stack Segment
Local variables (grows downward)malloc() Function - Complete Guide
The malloc() (memory allocation) function allocates a block of memory of specified size in bytes and returns a pointer to the beginning of the block. The allocated memory contains garbage values (uninitialized).
Syntax:
void* malloc(size_t size);
Parameters: size - Number of bytes to allocate
Return Value: Pointer to allocated memory, or NULL if allocation fails
Key Characteristics:
- Uninitialized Memory: Contains garbage values (random data)
- Return Type:
void*(generic pointer) - requires casting - NULL Check: Always check if malloc returns NULL
- Size Calculation: Use
sizeof()operator for portability - Memory Alignment: Returns memory aligned for any data type
- Heap Allocation: Memory comes from heap segment
Memory Lifecycle Visualization:
malloc() Examples:
#include <stdio.h>
#include <stdlib.h> // Required for malloc, free
#include <string.h>
int main() {
printf("=== BASIC malloc() USAGE ===\n\n");
printf("1. Allocating single variable:\n");
// Allocate memory for one integer
int *singleInt = (int*)malloc(sizeof(int));
// CRITICAL: Check if allocation succeeded
if(singleInt == NULL) {
printf(" Error: Memory allocation failed!\n");
return 1; // Exit with error code
}
// Use the allocated memory
*singleInt = 42;
printf(" Allocated integer: %d\n", *singleInt);
printf(" Address: %p\n", (void*)singleInt);
// Free the memory when done
free(singleInt);
singleInt = NULL; // Good practice to prevent dangling pointer
printf("\n2. Allocating array with malloc:\n");
int arraySize = 5;
// Allocate memory for 5 integers
int *dynamicArray = (int*)malloc(arraySize * sizeof(int));
if(dynamicArray == NULL) {
printf(" Error: Array allocation failed!\n");
return 1;
}
printf(" Array allocated at: %p\n", (void*)dynamicArray);
// Initialize array (contains garbage values initially)
printf(" Initial (garbage) values: ");
for(int i = 0; i < arraySize; i++) {
printf("%d ", dynamicArray[i]); // Random values
}
// Set values
for(int i = 0; i < arraySize; i++) {
dynamicArray[i] = i * 10;
}
printf("\n After initialization: ");
for(int i = 0; i < arraySize; i++) {
printf("%d ", dynamicArray[i]);
}
// Calculate memory size
size_t totalBytes = arraySize * sizeof(int);
printf("\n Total memory allocated: %zu bytes\n", totalBytes);
// Free the array
free(dynamicArray);
dynamicArray = NULL;
printf("\n3. String allocation with malloc:\n");
// Allocate memory for string (including null terminator)
char *dynamicString = (char*)malloc(50 * sizeof(char));
if(dynamicString == NULL) {
printf(" Error: String allocation failed!\n");
return 1;
}
// Copy string to allocated memory
strcpy(dynamicString, "Hello, Dynamic Memory!");
printf(" Dynamic string: %s\n", dynamicString);
printf(" String length: %zu\n", strlen(dynamicString));
printf(" Memory used: %zu bytes\n", strlen(dynamicString) + 1);
free(dynamicString);
dynamicString = NULL;
printf("\n4. Structure allocation:\n");
typedef struct {
int id;
char name[50];
float salary;
} Employee;
// Allocate memory for one Employee
Employee *emp = (Employee*)malloc(sizeof(Employee));
if(emp != NULL) {
emp->id = 101;
strcpy(emp->name, "John Doe");
emp->salary = 55000.50;
printf(" Employee: %d, %s, $%.2f\n",
emp->id, emp->name, emp->salary);
printf(" Structure size: %zu bytes\n", sizeof(Employee));
free(emp);
emp = NULL;
}
printf("\n5. Common malloc() mistakes:\n");
// Mistake 1: Forgetting to check NULL
int *badPtr = (int*)malloc(1000000000 * sizeof(int)); // Too large!
// if(badPtr == NULL) ... // MISSING: Program may crash
// Mistake 2: Wrong size calculation
// int *wrongSize = malloc(5); // WRONG: Should be 5 * sizeof(int)
// Mistake 3: Not freeing memory (memory leak)
// int *leaky = malloc(sizeof(int));
// Use leaky...
// Forgot: free(leaky); // MEMORY LEAK!
// Mistake 4: Using freed memory
// int *dangling = malloc(sizeof(int));
// free(dangling);
// *dangling = 10; // DANGEROUS: Use after free
printf(" Always: Check NULL, Calculate size correctly, Free memory\n");
return 0;
}
=== BASIC malloc() USAGE ===
1. Allocating single variable:
Allocated integer: 42
Address: 0x55a1b2c3d4e5 (example)
2. Allocating array with malloc:
Array allocated at: 0x55a1b2c3d510 (example)
Initial (garbage) values: 0 0 4196432 0 0 (random)
After initialization: 0 10 20 30 40
Total memory allocated: 20 bytes
3. String allocation with malloc:
Dynamic string: Hello, Dynamic Memory!
String length: 22
Memory used: 23 bytes
4. Structure allocation:
Employee: 101, John Doe, $55000.50
Structure size: 60 bytes
5. Common malloc() mistakes:
Always: Check NULL, Calculate size correctly, Free memory
- Always include
<stdlib.h> - Always check if malloc returns
NULL - Use
sizeof()for correct size calculation - Cast the return value to appropriate type
- Initialize memory before use (contains garbage)
- Always call
free()when done - Set pointer to
NULLafterfree() - Don't call
free()on non-malloc memory
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Safe malloc wrapper
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if(ptr == NULL) {
fprintf(stderr, "Fatal error: Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
return ptr;
}
// Function to demonstrate dynamic array growth
int* create_dynamic_array(int initial_size) {
int *arr = (int*)safe_malloc(initial_size * sizeof(int));
// Initialize with sequence
for(int i = 0; i < initial_size; i++) {
arr[i] = i * i; // Squares
}
return arr;
}
// Function to demonstrate ragged array (array of strings)
char** create_string_array(int count, const char* prefix) {
// Allocate array of string pointers
char **strings = (char**)safe_malloc(count * sizeof(char*));
for(int i = 0; i < count; i++) {
// Calculate needed memory for this string
int needed = snprintf(NULL, 0, "%s%d", prefix, i) + 1;
// Allocate exact amount needed
strings[i] = (char*)safe_malloc(needed * sizeof(char));
// Create the string
snprintf(strings[i], needed, "%s%d", prefix, i);
}
return strings;
}
int main() {
printf("=== ADVANCED malloc() PATTERNS ===\n\n");
printf("1. Dynamic 2D array using single malloc:\n");
int rows = 3, cols = 4;
// Allocate contiguous memory for entire matrix
int *matrix = (int*)safe_malloc(rows * cols * sizeof(int));
// Initialize as flat array
for(int i = 0; i < rows * cols; i++) {
matrix[i] = i + 1;
}
// Access using row-major order: matrix[row * cols + col]
printf(" Matrix (%dx%d):\n", rows, cols);
for(int i = 0; i < rows; i++) {
printf(" ");
for(int j = 0; j < cols; j++) {
printf("%3d ", matrix[i * cols + j]);
}
printf("\n");
}
free(matrix);
matrix = NULL;
printf("\n2. Dynamic 2D array using array of pointers:\n");
// Allocate array of row pointers
int **matrix2 = (int**)safe_malloc(rows * sizeof(int*));
// Allocate each row separately
for(int i = 0; i < rows; i++) {
matrix2[i] = (int*)safe_malloc(cols * sizeof(int));
for(int j = 0; j < cols; j++) {
matrix2[i][j] = (i + 1) * (j + 1); // Multiplication table
}
}
printf(" Matrix2 (%dx%d):\n", rows, cols);
for(int i = 0; i < rows; i++) {
printf(" ");
for(int j = 0; j < cols; j++) {
printf("%3d ", matrix2[i][j]);
}
printf("\n");
}
// Free each row, then the array of pointers
for(int i = 0; i < rows; i++) {
free(matrix2[i]);
matrix2[i] = NULL;
}
free(matrix2);
matrix2 = NULL;
printf("\n3. Ragged array (array of strings):\n");
int string_count = 4;
char **strings = create_string_array(string_count, "Item");
printf(" String array:\n");
for(int i = 0; i < string_count; i++) {
printf(" [%d] %s (length: %zu)\n",
i, strings[i], strlen(strings[i]));
}
// Free each string, then the array
for(int i = 0; i < string_count; i++) {
free(strings[i]);
strings[i] = NULL;
}
free(strings);
strings = NULL;
printf("\n4. Memory allocation for function return:\n");
int initial_size = 5;
int *dynamic_array = create_dynamic_array(initial_size);
printf(" Dynamic array from function:\n ");
for(int i = 0; i < initial_size; i++) {
printf("%d ", dynamic_array[i]);
}
printf("\n");
// Important: Caller must free this memory!
free(dynamic_array);
dynamic_array = NULL;
printf("\n5. Memory allocation patterns comparison:\n");
// Pattern A: Single allocation (contiguous)
int *patternA = (int*)safe_malloc(100 * sizeof(int));
// Pros: Contiguous, cache-friendly
// Cons: Fixed size, hard to resize
// Pattern B: Array of pointers
int **patternB = (int**)safe_malloc(10 * sizeof(int*));
for(int i = 0; i < 10; i++) {
patternB[i] = (int*)safe_malloc(10 * sizeof(int));
}
// Pros: Flexible rows, easy to resize rows independently
// Cons: Not contiguous, more allocations/frees
// Cleanup pattern A
free(patternA);
patternA = NULL;
// Cleanup pattern B
for(int i = 0; i < 10; i++) {
free(patternB[i]);
patternB[i] = NULL;
}
free(patternB);
patternB = NULL;
printf(" Pattern A: Single allocation - fast, contiguous\n");
printf(" Pattern B: Array of pointers - flexible, independent rows\n");
return 0;
}
calloc() Function - Complete Guide
The calloc() (contiguous allocation) function allocates memory for an array of elements, initializes all bytes to zero, and returns a pointer to the memory. It's ideal for arrays and structures that need zero initialization.
Syntax:
void* calloc(size_t num, size_t size);
Parameters: num - Number of elements, size - Size of each element in bytes
Return Value: Pointer to allocated memory (all bits zero), or NULL if allocation fails
Key Characteristics:
- Zero-Initialized: All bits set to zero (0 for integers, 0.0 for floats, NULL for pointers)
- Two Parameters: Takes number of elements and size of each element
- Array-Oriented: Designed for allocating arrays
- Slower than malloc: Extra overhead for zero initialization
- Memory Calculation: Total memory = num × size bytes
- Safer: Zero initialization prevents garbage values
calloc() vs malloc() Comparison:
| Feature | malloc() | calloc() |
|---|---|---|
| Parameters | 1 (total bytes) | 2 (count, element size) |
| Initialization | Garbage values | All bits zero |
| Speed | Faster | Slower (initialization overhead) |
| Use Case | General allocation | Arrays, need initialization |
| Syntax Example | malloc(10 * sizeof(int)) |
calloc(10, sizeof(int)) |
| Memory Content | Unpredictable | Predictable (zeros) |
calloc() Examples:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
printf("=== COMPREHENSIVE calloc() USAGE ===\n\n");
printf("1. Basic calloc() for arrays:\n");
int array_size = 5;
// Allocate and zero-initialize array of 5 integers
int *zero_array = (int*)calloc(array_size, sizeof(int));
if(zero_array == NULL) {
printf(" Error: calloc() failed!\n");
return 1;
}
printf(" calloc() array (all zeros): ");
for(int i = 0; i < array_size; i++) {
printf("%d ", zero_array[i]); // All zeros
}
// Compare with malloc()
int *garbage_array = (int*)malloc(array_size * sizeof(int));
printf("\n malloc() array (garbage): ");
for(int i = 0; i < array_size; i++) {
printf("%d ", garbage_array[i]); // Random values
}
free(zero_array);
free(garbage_array);
printf("\n\n2. Structure array with calloc():\n");
typedef struct {
int id;
char name[30];
float score;
int *grades;
} Student;
int student_count = 3;
// Allocate array of Student structures
Student *students = (Student*)calloc(student_count, sizeof(Student));
if(students == NULL) {
printf(" Error: Student allocation failed!\n");
return 1;
}
printf(" Student structures after calloc():\n");
for(int i = 0; i < student_count; i++) {
printf(" Student %d: id=%d, name='%s', score=%.2f, grades=%p\n",
i, students[i].id,
students[i].name, // Empty string (first char is '\0')
students[i].score, // 0.0
(void*)students[i].grades); // NULL pointer
}
// Initialize students
for(int i = 0; i < student_count; i++) {
students[i].id = 1000 + i;
snprintf(students[i].name, 30, "Student%d", i);
students[i].score = 75.5 + i * 5;
// Allocate grades array for each student
students[i].grades = (int*)calloc(5, sizeof(int));
for(int j = 0; j < 5; j++) {
students[i].grades[j] = 60 + (i * 5) + j;
}
}
printf("\n After initialization:\n");
for(int i = 0; i < student_count; i++) {
printf(" Student %d: %s, Score: %.1f, Grades: ",
students[i].id, students[i].name, students[i].score);
for(int j = 0; j < 5; j++) {
printf("%d ", students[i].grades[j]);
}
printf("\n");
}
// Free nested allocations first
for(int i = 0; i < student_count; i++) {
free(students[i].grades);
students[i].grades = NULL;
}
// Then free the main array
free(students);
students = NULL;
printf("\n3. 2D array with calloc():\n");
int rows = 3, cols = 4;
// Method 1: Single calloc() for contiguous memory
int *matrix1 = (int*)calloc(rows * cols, sizeof(int));
// Initialize with values
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
matrix1[i * cols + j] = i * cols + j + 1;
}
}
printf(" Matrix1 (contiguous):\n");
for(int i = 0; i < rows; i++) {
printf(" ");
for(int j = 0; j < cols; j++) {
printf("%3d ", matrix1[i * cols + j]);
}
printf("\n");
}
free(matrix1);
matrix1 = NULL;
// Method 2: Array of pointers with calloc()
printf("\n Matrix2 (array of pointers):\n");
int **matrix2 = (int**)calloc(rows, sizeof(int*));
for(int i = 0; i < rows; i++) {
matrix2[i] = (int*)calloc(cols, sizeof(int));
for(int j = 0; j < cols; j++) {
matrix2[i][j] = (i + 1) * (j + 1);
}
}
for(int i = 0; i < rows; i++) {
printf(" ");
for(int j = 0; j < cols; j++) {
printf("%3d ", matrix2[i][j]);
}
printf("\n");
}
// Free matrix2
for(int i = 0; i < rows; i++) {
free(matrix2[i]);
matrix2[i] = NULL;
}
free(matrix2);
matrix2 = NULL;
printf("\n4. String array with calloc():\n");
int word_count = 4;
int max_word_len = 20;
// Allocate array of string pointers (all NULL initially)
char **words = (char**)calloc(word_count, sizeof(char*));
// Allocate and initialize each string
const char *word_list[] = {"apple", "banana", "cherry", "date"};
for(int i = 0; i < word_count; i++) {
// Allocate exact size for each word
words[i] = (char*)calloc(strlen(word_list[i]) + 1, sizeof(char));
strcpy(words[i], word_list[i]);
}
printf(" String array:\n");
for(int i = 0; i < word_count; i++) {
printf(" [%d] '%s' (length: %zu)\n",
i, words[i], strlen(words[i]));
}
// Free strings
for(int i = 0; i < word_count; i++) {
free(words[i]);
words[i] = NULL;
}
free(words);
words = NULL;
printf("\n5. When to use calloc() vs malloc():\n");
printf(" Use calloc() when:\n");
printf(" • You need zero-initialized memory\n");
printf(" • Working with arrays of numeric types\n");
printf(" • Allocating structures with pointer fields (initialized to NULL)\n");
printf(" • Security-sensitive data (no leftover data from previous use)\n");
printf("\n Use malloc() when:\n");
printf(" • Performance is critical\n");
printf(" • You'll immediately overwrite all values\n");
printf(" • Memory will be used as raw buffer\n");
printf("\n6. Performance consideration:\n");
// Performance test
const int large_size = 1000000;
clock_t start, end;
double malloc_time, calloc_time;
// Time malloc
start = clock();
int *malloc_test = (int*)malloc(large_size * sizeof(int));
// Initialize manually
for(int i = 0; i < large_size; i++) {
malloc_test[i] = 0;
}
end = clock();
malloc_time = ((double)(end - start)) / CLOCKS_PER_SEC;
free(malloc_test);
// Time calloc
start = clock();
int *calloc_test = (int*)calloc(large_size, sizeof(int));
end = clock();
calloc_time = ((double)(end - start)) / CLOCKS_PER_SEC;
free(calloc_test);
printf(" For %d integers:\n", large_size);
printf(" • malloc() + manual zero: %.4f seconds\n", malloc_time);
printf(" • calloc(): %.4f seconds\n", calloc_time);
printf(" Note: calloc() may use optimized zeroing\n");
return 0;
}
calloc() not only sets memory to zero but also ensures memory is "clean" from any previous data. This is particularly important for security-sensitive applications where leftover data in memory could be a security risk. Additionally, some operating systems implement calloc() using lazy allocation, where physical memory isn't actually allocated until it's written to.
realloc() Function - Complete Guide
The realloc() (reallocate) function changes the size of a previously allocated memory block. It can expand or shrink the allocation while preserving existing data (as much as possible).
Syntax:
void* realloc(void* ptr, size_t new_size);
Parameters: ptr - Pointer to previously allocated memory, new_size - New size in bytes
Return Value: Pointer to reallocated memory (may be different), or NULL if allocation fails
Key Characteristics:
- Size Change: Can increase or decrease allocation size
- Data Preservation: Preserves existing data up to minimum of old and new size
- Pointer May Change: May return different pointer address
- NULL Pointer: If ptr is NULL, realloc() behaves like malloc()
- Zero Size: If new_size is 0 and ptr is not NULL, memory is freed
- Performance: May need to copy data if can't expand in place
realloc() Behavior Scenarios:
| Scenario | realloc() Behavior | Result |
|---|---|---|
| ptr is NULL | Acts like malloc(new_size) | New allocation |
| new_size is 0 | Acts like free(ptr) | Memory freed, returns NULL |
| Enough space after block | Extends in place | Same pointer, larger size |
| Not enough space after | Allocates new block, copies data, frees old | New pointer, old data copied |
| Smaller new_size | Shrinks block (may free excess) | Same pointer, smaller size |
| Allocation fails | Returns NULL, original block unchanged | Need to handle failure |
realloc() Examples:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Safe realloc wrapper
void* safe_realloc(void *ptr, size_t new_size) {
void *new_ptr = realloc(ptr, new_size);
if(new_ptr == NULL && new_size > 0) {
fprintf(stderr, "Error: realloc() failed!\n");
free(ptr); // Free old memory on failure
exit(EXIT_FAILURE);
}
return new_ptr;
}
// Dynamic array implementation
typedef struct {
int *data;
size_t size;
size_t capacity;
} DynamicArray;
DynamicArray* create_dynamic_array(size_t initial_capacity) {
DynamicArray *arr = (DynamicArray*)malloc(sizeof(DynamicArray));
arr->data = (int*)calloc(initial_capacity, sizeof(int));
arr->size = 0;
arr->capacity = initial_capacity;
return arr;
}
void push_back(DynamicArray *arr, int value) {
// If array is full, double the capacity
if(arr->size >= arr->capacity) {
size_t new_capacity = arr->capacity * 2;
if(new_capacity == 0) new_capacity = 1;
arr->data = (int*)safe_realloc(arr->data, new_capacity * sizeof(int));
// Zero out new elements
for(size_t i = arr->capacity; i < new_capacity; i++) {
arr->data[i] = 0;
}
arr->capacity = new_capacity;
printf(" Resized: %zu -> %zu elements\n", arr->capacity/2, arr->capacity);
}
arr->data[arr->size] = value;
arr->size++;
}
int main() {
printf("=== COMPREHENSIVE realloc() USAGE ===\n\n");
printf("1. Basic realloc() operations:\n");
// Start with small array
int *arr = (int*)malloc(3 * sizeof(int));
for(int i = 0; i < 3; i++) {
arr[i] = (i + 1) * 10;
}
printf(" Original array (size 3): ");
for(int i = 0; i < 3; i++) {
printf("%d ", arr[i]);
}
// Expand array
arr = (int*)realloc(arr, 6 * sizeof(int));
if(arr == NULL) {
printf(" Error: realloc() failed!\n");
free(arr);
return 1;
}
// Initialize new elements
for(int i = 3; i < 6; i++) {
arr[i] = (i + 1) * 10;
}
printf("\n After expansion (size 6): ");
for(int i = 0; i < 6; i++) {
printf("%d ", arr[i]);
}
// Shrink array
arr = (int*)realloc(arr, 4 * sizeof(int));
printf("\n After shrink (size 4): ");
for(int i = 0; i < 4; i++) {
printf("%d ", arr[i]);
}
free(arr);
arr = NULL;
printf("\n\n2. String resizing with realloc():\n");
char *str = (char*)malloc(10 * sizeof(char));
strcpy(str, "Hello");
printf(" Original: '%s' (capacity: 10)\n", str);
// Need more space
str = (char*)realloc(str, 50 * sizeof(char));
strcat(str, ", World! This is a longer string.");
printf(" After realloc: '%s' (capacity: 50)\n", str);
// Shrink to fit exact size
size_t needed = strlen(str) + 1;
str = (char*)realloc(str, needed * sizeof(char));
printf(" After exact fit: '%s' (capacity: %zu)\n", str, needed);
free(str);
str = NULL;
printf("\n3. Dynamic array implementation:\n");
DynamicArray *dyn_arr = create_dynamic_array(2);
printf(" Adding 10 elements:\n");
for(int i = 1; i <= 10; i++) {
push_back(dyn_arr, i * 100);
printf(" Added %d, size: %zu, capacity: %zu\n",
i * 100, dyn_arr->size, dyn_arr->capacity);
}
printf("\n Final array (%zu elements):\n ", dyn_arr->size);
for(size_t i = 0; i < dyn_arr->size; i++) {
printf("%d ", dyn_arr->data[i]);
if((i + 1) % 5 == 0 && i + 1 < dyn_arr->size) {
printf("\n ");
}
}
// Cleanup
free(dyn_arr->data);
free(dyn_arr);
printf("\n\n4. Common realloc() patterns:\n");
// Pattern 1: Reallocating with temporary variable
printf(" Pattern 1: Using temp variable for safety:\n");
int *data = (int*)malloc(5 * sizeof(int));
for(int i = 0; i < 5; i++) data[i] = i + 1;
int *temp = (int*)realloc(data, 10 * sizeof(int));
if(temp == NULL) {
printf(" Realloc failed, keeping original\n");
// data still valid with original 5 elements
} else {
data = temp; // Only assign if realloc succeeded
printf(" Realloc succeeded\n");
}
free(data);
// Pattern 2: Growing array exponentially
printf("\n Pattern 2: Exponential growth (amortized O(1)):\n");
int *exp_arr = NULL;
size_t exp_capacity = 0;
size_t exp_size = 0;
for(int i = 0; i < 20; i++) {
if(exp_size >= exp_capacity) {
// Double capacity (or start with 1)
size_t new_capacity = exp_capacity == 0 ? 1 : exp_capacity * 2;
int *new_arr = (int*)realloc(exp_arr, new_capacity * sizeof(int));
if(new_arr == NULL) {
printf(" Growth failed at i=%d\n", i);
break;
}
exp_arr = new_arr;
exp_capacity = new_capacity;
printf(" Grow to %zu elements\n", exp_capacity);
}
exp_arr[exp_size] = i * 10;
exp_size++;
}
free(exp_arr);
printf("\n5. realloc() with different data types:\n");
// Structure reallocation
typedef struct {
char name[50];
int age;
} Person;
Person *people = (Person*)malloc(2 * sizeof(Person));
strcpy(people[0].name, "Alice");
people[0].age = 30;
strcpy(people[1].name, "Bob");
people[1].age = 25;
printf(" Original people array:\n");
for(int i = 0; i < 2; i++) {
printf(" %s, %d\n", people[i].name, people[i].age);
}
// Add more people
people = (Person*)realloc(people, 4 * sizeof(Person));
strcpy(people[2].name, "Charlie");
people[2].age = 35;
strcpy(people[3].name, "Diana");
people[3].age = 28;
printf(" After realloc (4 people):\n");
for(int i = 0; i < 4; i++) {
printf(" %s, %d\n", people[i].name, people[i].age);
}
free(people);
printf("\n6. Important realloc() warnings:\n");
// WARNING 1: Don't use original pointer after failed realloc
int *warning1 = (int*)malloc(5 * sizeof(int));
int *original = warning1; // Save original
warning1 = (int*)realloc(warning1, 1000000000000ULL * sizeof(int));
if(warning1 == NULL) {
printf(" Warning 1: realloc() failed\n");
// original is still valid here!
// free(original); // Should free original
}
// WARNING 2: Zero-size realloc frees memory
int *warning2 = (int*)malloc(10 * sizeof(int));
warning2 = (int*)realloc(warning2, 0); // Equivalent to free(warning2)
// warning2 is now NULL
printf(" Always: Check return value, Handle failure gracefully\n");
return 0;
}
- Always use temporary variable:
new_ptr = realloc(old_ptr, size) - Check if realloc returns
NULLbefore assigning - If realloc fails, original pointer is still valid
realloc(NULL, size)behaves likemalloc(size)realloc(ptr, 0)behaves likefree(ptr)- When expanding, new memory area is uninitialized
- When shrinking, excess memory is freed (data may be lost)
- Pointer may change address - update all references
free() Function - Complete Guide
The free() function deallocates memory previously allocated by malloc(), calloc(), or realloc(). It returns the memory to the heap for reuse. Proper memory deallocation is crucial to prevent memory leaks.
Syntax:
void free(void* ptr);
Parameters: ptr - Pointer to memory block to free
Return Value: None (void)
Key Characteristics:
- Memory Deallocation: Returns memory to heap
- No Return Value: Function returns void
- NULL Safe:
free(NULL)does nothing (safe to call) - Dangling Pointers: Pointer becomes invalid after free()
- Memory Leak: Forgetting to free() causes memory leak
- Double Free: Calling free() twice on same pointer causes undefined behavior
- Heap Corruption: Freeing non-heap memory or corrupted pointers
Memory Leak Visualization:
Memory Leak Over Time
Memory blocks that are allocated but never freed become leaks, reducing available memory over time
free() Examples:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Safe free wrapper that sets pointer to NULL
#define SAFE_FREE(ptr) \
do { \
free(ptr); \
(ptr) = NULL; \
} while(0)
// Function demonstrating proper cleanup
char* create_greeting(const char *name) {
// Calculate needed memory
size_t len = strlen("Hello, ") + strlen(name) + strlen("!") + 1;
char *greeting = (char*)malloc(len * sizeof(char));
if(greeting == NULL) {
return NULL;
}
snprintf(greeting, len, "Hello, %s!", name);
return greeting;
}
// Complex structure with nested allocations
typedef struct {
char *name;
int *scores;
int score_count;
struct Node *next;
} Node;
Node* create_node(const char *name, int *scores, int count) {
Node *node = (Node*)malloc(sizeof(Node));
if(node == NULL) return NULL;
// Allocate and copy name
node->name = (char*)malloc((strlen(name) + 1) * sizeof(char));
if(node->name == NULL) {
free(node);
return NULL;
}
strcpy(node->name, name);
// Allocate and copy scores
node->scores = (int*)malloc(count * sizeof(int));
if(node->scores == NULL) {
free(node->name);
free(node);
return NULL;
}
memcpy(node->scores, scores, count * sizeof(int));
node->score_count = count;
node->next = NULL;
return node;
}
// Proper cleanup for Node
void destroy_node(Node *node) {
if(node == NULL) return;
// Free nested allocations first
free(node->name);
free(node->scores);
// Then free the node itself
free(node);
}
int main() {
printf("=== COMPREHENSIVE free() USAGE ===\n\n");
printf("1. Basic free() operations:\n");
// Single allocation
int *single = (int*)malloc(sizeof(int));
*single = 42;
printf(" Before free: %d at %p\n", *single, (void*)single);
free(single);
// single is now a dangling pointer!
printf(" After free: pointer is dangling\n");
// Good practice: Set to NULL after free
single = NULL;
printf(" After NULL: pointer is safe\n");
printf("\n2. Array deallocation:\n");
int *array = (int*)malloc(5 * sizeof(int));
for(int i = 0; i < 5; i++) {
array[i] = i * 10;
}
printf(" Array contents: ");
for(int i = 0; i < 5; i++) {
printf("%d ", array[i]);
}
free(array);
array = NULL;
printf("\n Array freed and pointer set to NULL\n");
printf("\n3. Using SAFE_FREE macro:\n");
char *str = (char*)malloc(50 * sizeof(char));
strcpy(str, "This will be safely freed");
printf(" String: %s\n", str);
SAFE_FREE(str);
printf(" After SAFE_FREE: str = %p\n", (void*)str);
// Safe to call SAFE_FREE on NULL pointer
SAFE_FREE(str); // Does nothing (free(NULL) is safe)
printf("\n4. Function returning allocated memory:\n");
char *greeting = create_greeting("World");
if(greeting != NULL) {
printf(" Greeting: %s\n", greeting);
// Caller is responsible for freeing!
free(greeting);
greeting = NULL;
printf(" Greeting freed by caller\n");
}
printf("\n5. Complex structure cleanup:\n");
int test_scores[] = {85, 90, 78};
Node *student = create_node("Alice", test_scores, 3);
if(student != NULL) {
printf(" Created node: %s\n", student->name);
printf(" Scores: ");
for(int i = 0; i < student->score_count; i++) {
printf("%d ", student->scores[i]);
}
printf("\n");
// Proper cleanup
destroy_node(student);
student = NULL;
printf(" Node completely destroyed\n");
}
printf("\n6. Linked list with proper cleanup:\n");
// Create simple linked list
Node *head = NULL;
Node *current = NULL;
int scores1[] = {90, 85};
int scores2[] = {88, 92, 86};
int scores3[] = {95};
// Add nodes to list
head = create_node("Bob", scores1, 2);
if(head != NULL) {
current = head;
Node *second = create_node("Charlie", scores2, 3);
if(second != NULL) {
current->next = second;
current = second;
}
Node *third = create_node("Diana", scores3, 1);
if(third != NULL) {
current->next = third;
}
}
// Traverse and print list
printf(" Linked list:\n");
current = head;
while(current != NULL) {
printf(" • %s: ", current->name);
for(int i = 0; i < current->score_count; i++) {
printf("%d ", current->scores[i]);
}
printf("\n");
current = current->next;
}
// Properly free entire list
current = head;
while(current != NULL) {
Node *next = current->next;
destroy_node(current);
current = next;
}
head = NULL;
printf(" Entire list freed\n");
printf("\n7. Common free() errors:\n");
// ERROR 1: Double free
printf(" Error 1: Double free\n");
int *error1 = (int*)malloc(sizeof(int));
free(error1);
// free(error1); // ERROR: Double free - undefined behavior!
error1 = NULL; // Solution: Set to NULL after first free
// ERROR 2: Freeing stack memory
printf(" Error 2: Freeing stack memory\n");
int stack_var = 100;
int *error2 = &stack_var;
// free(error2); // ERROR: Not heap memory!
// ERROR 3: Freeing uninitialized pointer
printf(" Error 3: Freeing uninitialized pointer\n");
int *error3; // Uninitialized
// free(error3); // ERROR: Contains garbage address!
// ERROR 4: Freeing part of allocation
printf(" Error 4: Freeing part of allocation\n");
int *error4 = (int*)malloc(10 * sizeof(int));
// free(error4 + 5); // ERROR: Not the start of allocation!
free(error4); // Correct: Free from start
// ERROR 5: Using memory after free
printf(" Error 5: Use after free\n");
int *error5 = (int*)malloc(sizeof(int));
*error5 = 50;
free(error5);
// *error5 = 60; // ERROR: Dangling pointer!
error5 = NULL; // Solution: Set to NULL
printf("\n8. Memory leak demonstration:\n");
// This function would leak memory
void leaky_function() {
char *leak = (char*)malloc(100 * sizeof(char));
strcpy(leak, "This memory will leak");
// Forgot to free(leak)!
}
printf(" leaky_function() allocates memory but doesn't free it\n");
printf(" Result: Memory leak - allocated memory never returned\n");
printf("\n9. Best practices summary:\n");
printf(" • Always free() what you malloc()/calloc()/realloc()\n");
printf(" • Set pointers to NULL after free()\n");
printf(" • free(NULL) is safe - does nothing\n");
printf(" • Free in reverse order of allocation (nested structures)\n");
printf(" • Use tools like valgrind to detect memory leaks\n");
printf(" • Document memory ownership in your code\n");
printf(" • Consider using RAII patterns or garbage collection in C++\n");
return 0;
}
- Pair Allocation with Free: Every malloc/calloc should have a corresponding free
- Set to NULL After Free: Prevents dangling pointer errors
- Free in Reverse Order: For nested structures, free inner allocations first
- Use Memory Debuggers: Valgrind, AddressSanitizer to catch leaks
- Document Ownership: Clearly indicate who allocates and who frees
- Check Return Values: Always check if allocation functions succeed
- Avoid Memory Fragmentation: Reuse allocations when possible
- Use RAII Patterns: In C++, use constructors/destructors for automatic cleanup
Common Memory Errors and Solutions
Memory management errors are among the most common and difficult-to-debug issues in C programming. Understanding these errors and their solutions is crucial for writing robust programs.
Memory Error Prevention Strategies:
- Use Memory Debuggers: Valgrind, AddressSanitizer, Electric Fence
- Code Reviews: Have others review memory management code
- Static Analysis: Use tools like Coverity, Clang Analyzer
- Defensive Programming: Add asserts and sanity checks
- Memory Pools: For performance-critical code with fixed-size allocations
- RAII Patterns: In C++, use smart pointers (unique_ptr, shared_ptr)
- Testing: Write tests that stress memory usage
- Documentation: Clearly document memory ownership
- Avoid Manual Memory When Possible: Use standard containers in C++
- Educate Team: Ensure all team members understand memory management
// Compile with: gcc -g -o test test.c
// Run with: valgrind --leak-check=full ./test
#include <stdio.h>
#include <stdlib.h>
void memory_leak_example() {
int *leak = malloc(100 * sizeof(int));
// Forgot to free - MEMORY LEAK
}
void use_after_free() {
int *ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
*ptr = 100; // USE AFTER FREE
}
void double_free_example() {
int *ptr = malloc(sizeof(int));
free(ptr);
free(ptr); // DOUBLE FREE
}
int main() {
printf("Testing memory errors...\n");
// Uncomment to test specific errors
// memory_leak_example();
// use_after_free();
// double_free_example();
printf("Test completed.\n");
return 0;
}
==12345== Memcheck, a memory error detector
==12345== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12345== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==12345== Command: ./test
==12345==
Testing memory errors...
Test completed.
==12345==
==12345== HEAP SUMMARY:
==12345== in use at exit: 400 bytes in 1 blocks
==12345== total heap usage: 1 allocs, 0 frees, 400 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345== by 0x1086B1: memory_leak_example (test.c:6)
==12345== by 0x108721: main (test.c:24)
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 400 bytes in 1 blocks
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== possibly lost: 0 bytes in 0 blocks
==12345== still reachable: 0 bytes in 0 blocks
==12345== suppressed: 0 bytes in 0 blocks
==12345==
==12345== For counts of detected and suppressed errors, rerun with: -v
==12345== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Key Takeaways
- Dynamic memory is allocated from the heap at runtime using
malloc(),calloc(),realloc() - Static memory is allocated at compile time in stack/data segments
malloc()allocates uninitialized memory,calloc()allocates zero-initialized memoryrealloc()resizes existing allocations, may move memory to new location- Always check if allocation functions return
NULL - Every
malloc()/calloc()must have a correspondingfree() - Set pointers to
NULLafterfree()to prevent dangling pointers - Memory leaks occur when allocated memory is never freed
- Dangling pointers point to freed memory - causes undefined behavior
- Double free occurs when memory is freed twice - causes crashes
- Use
sizeof()for portable memory size calculations - For nested structures, free inner allocations before outer ones
- Use memory debuggers like Valgrind to detect memory errors
- Consider using
calloc()for arrays and structures needing initialization realloc()withNULLpointer behaves likemalloc()realloc()with size 0 behaves likefree()- Document memory ownership clearly in your code