7. Dynamic Memory Allocation & Array
7.1 Introduction to Dynamic Memory
Up until now, we've used static memory allocation where array sizes must be known at compile time:
int arr[100]; // Size fixed at compile time
Problems with static allocation:
- Waste memory if you allocate too much
- Run out of space if you allocate too little
- Cannot adjust size during program execution
Dynamic memory allocation solves these problems by allowing you to allocate memory at runtime using special functions.
7.2 Memory Layout in C
┌─────────────────────────────────┐
│ Stack │ ← Local variables, function calls
│ (grows downward) │ Automatically managed
│ ↓ │
├─────────────────────────────────┤
│ │
│ (free space) │
│ │
│ ↑ │
├─────────────────────────────────┤
│ Heap │ ← Dynamic memory allocation
│ (grows upward) │ Manually managed (malloc/free)
├─────────────────────────────────┤
│ Data Segment │ ← Global and static variables
├─────────────────────────────────┤
│ Code Segment │ ← Program instructions
└─────────────────────────────────┘
7.3 Dynamic Memory Functions
C provides four main functions for dynamic memory management (defined in <stdlib.h>
):
Function | Purpose | Syntax |
---|---|---|
malloc() |
Allocates memory | void* malloc(size_t size) |
calloc() |
Allocates and initializes to zero | void* calloc(size_t n, size_t size) |
realloc() |
Resizes allocated memory | void* realloc(void* ptr, size_t size) |
free() |
Releases allocated memory | void free(void* ptr) |
7.4 malloc() - Memory Allocation
Purpose: Allocates a block of memory of specified size (in bytes)
Syntax:
void* malloc(size_t size);
Returns:
- Pointer to allocated memory on success
NULL
if allocation fails
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5;
// Allocate memory for 5 integers
ptr = (int*)malloc(n * sizeof(int));
// Check if allocation was successful
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Use the allocated memory
for (int i = 0; i < n; i++) {
ptr[i] = i * 10;
}
// Print values
printf("Values: ");
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
// Free the allocated memory
free(ptr);
ptr = NULL; // Good practice
return 0;
}
Important Notes:
- Always multiply by
sizeof(data_type)
to get correct byte size - Always check if
malloc()
returnsNULL
- Always cast the return value:
(int*)malloc(...)
- Memory allocated by
malloc()
contains garbage values
7.5 calloc() - Contiguous Allocation
Purpose: Allocates memory and initializes all bytes to zero
Syntax:
void* calloc(size_t n, size_t size);
Parameters:
n
: Number of elementssize
: Size of each element
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5;
// Allocate and initialize memory for 5 integers
ptr = (int*)calloc(n, sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Print values (all will be 0)
printf("Initial values: ");
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
free(ptr);
ptr = NULL;
return 0;
}
/* Output:
Initial values: 0 0 0 0 0
*/
malloc() vs calloc():
Feature | malloc() | calloc() |
---|---|---|
Parameters | 1 (total bytes) | 2 (number, size) |
Initialization | Garbage values | All zeros |
Speed | Faster | Slightly slower |
Use case | When you'll initialize values | When you need zero-initialized memory |
7.6 realloc() - Resize Memory
Purpose: Changes the size of previously allocated memory
Syntax:
void* realloc(void* ptr, size_t new_size);
Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n = 5;
// Initial allocation
ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Fill initial array
for (int i = 0; i < n; i++) {
ptr[i] = i + 1;
}
printf("Initial array (size %d): ", n);
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
// Resize to 10 elements
n = 10;
ptr = (int*)realloc(ptr, n * sizeof(int));
if (ptr == NULL) {
printf("Memory reallocation failed!\n");
return 1;
}
// Fill new elements
for (int i = 5; i < n; i++) {
ptr[i] = i + 1;
}
printf("Resized array (size %d): ", n);
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
free(ptr);
ptr = NULL;
return 0;
}
/* Output:
Initial array (size 5): 1 2 3 4 5
Resized array (size 10): 1 2 3 4 5 6 7 8 9 10
*/
Important Notes about realloc():
- If
new_size
is larger, existing data is preserved, new space is uninitialized - If
new_size
is smaller, data is truncated - May move the block to a new location (address may change)
- If realloc fails, original pointer remains valid
- If
ptr
isNULL
, behaves likemalloc()
7.7 free() - Deallocate Memory
Purpose: Releases memory back to the system
Syntax:
void free(void* ptr);
Example:
int *ptr = (int*)malloc(100 * sizeof(int));
// Use the memory...
free(ptr); // Release memory
ptr = NULL; // Set to NULL to avoid dangling pointer
Important Rules:
- Only free memory that was allocated with malloc/calloc/realloc
- Free each block exactly once
- Don't use memory after freeing it
- Set pointer to NULL after freeing (good practice)
7.8 Dynamic Arrays - Complete Example
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
int *arr;
printf("Enter number of elements: ");
scanf("%d", &n);
// Allocate memory dynamically
arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Input values
printf("Enter %d integers:\n", n);
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
// Process: find sum and average
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
double average = (double)sum / n;
// Output results
printf("\nArray elements: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\nSum: %d\n", sum);
printf("Average: %.2f\n", average);
// Free allocated memory
free(arr);
arr = NULL;
return 0;
}
7.9 Dynamic 2D Arrays
Method 1: Array of Pointers (Rows can have different lengths)
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
int **matrix;
// Allocate array of row pointers
matrix = (int**)malloc(rows * sizeof(int*));
// Allocate each row
for (int i = 0; i < rows; i++) {
matrix[i] = (int*)malloc(cols * sizeof(int));
}
// Fill matrix
int value = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = value++;
}
}
// Print matrix
printf("Matrix:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
// Free memory
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
matrix = NULL;
return 0;
}
Method 2: Single Contiguous Block
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
int *matrix;
// Allocate as single block
matrix = (int*)malloc(rows * cols * sizeof(int));
if (matrix == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Fill matrix using formula: matrix[i][j] = matrix[i * cols + j]
int value = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i * cols + j] = value++;
}
}
// Print matrix
printf("Matrix:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i * cols + j]);
}
printf("\n");
}
// Free memory (single free needed)
free(matrix);
matrix = NULL;
return 0;
}
7.10 Dynamic Memory Best Practices
1. Always Check for NULL
int *ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed!\n");
exit(1);
}
2. Always Free Allocated Memory
// Allocate
int *data = (int*)malloc(100 * sizeof(int));
// Use data...
// Free when done
free(data);
data = NULL;
3. Don't Double Free
int *ptr = (int*)malloc(10 * sizeof(int));
free(ptr);
free(ptr); // ERROR! Double free - undefined behavior
4. Don't Use After Free
int *ptr = (int*)malloc(10 * sizeof(int));
free(ptr);
ptr[0] = 5; // ERROR! Using freed memory
5. Match Every malloc with free
void function() {
int *ptr = (int*)malloc(100 * sizeof(int));
// Use ptr...
free(ptr); // Don't forget!
}
7.11 Memory Leaks
A memory leak occurs when allocated memory is not freed:
Example of Memory Leak:
void badFunction() {
int *ptr = (int*)malloc(1000 * sizeof(int));
// Use ptr...
return; // BUG! Memory not freed - leaked!
}
int main() {
for (int i = 0; i < 1000; i++) {
badFunction(); // Leaks memory every iteration
}
return 0;
}
Fixed Version:
void goodFunction() {
int *ptr = (int*)malloc(1000 * sizeof(int));
// Use ptr...
free(ptr); // Properly freed
return;
}
Another Common Leak:
int *ptr = (int*)malloc(100 * sizeof(int));
ptr = (int*)malloc(200 * sizeof(int)); // LEAK! Lost reference to first block
Fixed:
int *ptr = (int*)malloc(100 * sizeof(int));
free(ptr); // Free first
ptr = (int*)malloc(200 * sizeof(int)); // Then allocate new
No comments to display
No comments to display