Lab 1 memory management explained
Stack-Allocated Arrays vs Dynamic
Allocation (malloc/free)
Stack-Allocated Arrays
What are Stack-Allocated Arrays?
Definition: Arrays declared as local variables that are automatically managed by the compiler.
void function_example() {
int arr[5]; // ← This is stack-allocated
// Memory is automatically allocated when function starts
// Memory is automatically freed when function ends
}
Memory Layout Visualization
STACK MEMORY (grows downward)
│
▼
┌─────────────────┐ ← Higher addresses
│ Function Call │
│ Frame │
├─────────────────┤
│ arr[4] = ? │ ← Address: 1016
│ arr[3] = ? │ ← Address: 1012
│ arr[2] = ? │ ← Address: 1008
│ arr[1] = ? │ ← Address: 1004
│ arr[0] = ? │ ← Address: 1000
├─────────────────┤
│ Other local │
│ variables │
└─────────────────┘ ← Lower addresses (stack pointer)
Characteristics of Stack Arrays
Advantages:
Fast allocation/deallocation (just moving stack pointer)
Automatic cleanup (no memory leaks possible)
Simple syntax (no pointers needed)
Cache-friendly (contiguous memory)
Limitations:
Fixed size (must be known at compile time)
Limited size (stack space is limited, typically 1-8MB)
Scope-limited (destroyed when function exits)
Cannot resize during runtime
Example Code
#include <stdio.h>
void stack_array_example() {
// Stack allocation - size must be compile-time constant
int grades[5]; // 5 * 4 = 20 bytes on stack
// Initialize array
for(int i = 0; i < 5; i++) {
grades[i] = (i + 1) * 10; // 10, 20, 30, 40, 50
}
// Print array
printf("Stack array: ");
for(int i = 0; i < 5; i++) {
printf("%d ", grades[i]);
}
printf("\n");
// Memory automatically freed when function exits
}
What happens in memory:
1. Function called → Stack frame created
2. 20 bytes allocated for grades array
3. Array used normally
4. Function exits → Stack frame destroyed, memory automatically reclaimed
Dynamic Allocation (malloc/free)
What is Dynamic Allocation?
Definition: Memory allocated at runtime from the heap, manually managed by programmer.
int* arr = malloc(5 * sizeof(int)); // ← Dynamic allocation
// YOU must call free(arr) when done!
Memory Layout Visualization
HEAP MEMORY (grows upward)
│
▼
┌─────────────────┐ ← Lower addresses
│ │
│ Free Space │
│ │
├─────────────────┤
│ arr[0] = ? │ ← Address: 5000 (returned by malloc)
│ arr[1] = ? │ ← Address: 5004
│ arr[2] = ? │ ← Address: 5008
│ arr[3] = ? │ ← Address: 5012
│ arr[4] = ? │ ← Address: 5016
├─────────────────┤
│ │
│ Free Space │
│ │
└─────────────────┘ ← Higher addresses
STACK MEMORY
┌─────────────────┐
│ int* arr = 5000 │ ← Pointer variable on stack
│ │ (contains address 5000)
└─────────────────┘
malloc() Function
Syntax: void* malloc(size_t size)
What it does:
1. Allocates size bytes from heap
2. Returns pointer to first byte
3. Memory contents are undefined (garbage values)
4. Returns NULL if allocation fails
Example:
// Allocate space for 5 integers
int* arr = malloc(5 * sizeof(int));
// └─────┘ └───────────┘
// function parameter
// (20 bytes on most systems)
free() Function
Syntax: void free(void* ptr)
What it does:
1. Returns memory to heap for reuse
2. Does NOT change pointer value
3. Accessing freed memory = undefined behavior
4. Calling free(NULL) is safe (does nothing)
Example:
free(arr); // Return memory to heap
arr = NULL; // Good practice: set pointer to NULL
Complete Dynamic Allocation Example
#include <stdio.h>
#include <stdlib.h>
void dynamic_array_example() {
int size;
printf("Enter array size: ");
scanf("%d", &size);
// Step 1: Allocate memory dynamically
int* arr = malloc(size * sizeof(int));
// Step 2: Check if allocation succeeded
if (arr == NULL) {
printf("Memory allocation failed!\n");
return;
}
printf("Successfully allocated %d bytes\n", size * (int)sizeof(int));
// Step 3: Use the array normally
for (int i = 0; i < size; i++) {
arr[i] = i * i; // Store squares: 0, 1, 4, 9, 16...
}
// Step 4: Print the array
printf("Dynamic array: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Step 5: CRITICAL - Free the memory
free(arr);
arr = NULL; // Prevent accidental reuse
printf("Memory freed successfully\n");
}
Key Differences Comparison
Aspect Stack Arrays Dynamic Arrays (malloc)
Size Fixed at compile time Determined at runtime
Syntax int arr[5] int* arr = malloc(...)
Speed Very fast Slightly slower
Memory Stack (limited) Heap (much larger)
Cleanup Automatic Manual (must call free)
Lifetime Function scope only Until you call free()
Risk Stack overflow Memory leaks, double free
Memory Management Best Practices
1. Always Check malloc() Return Value
int* arr = malloc(size * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "Memory allocation failed!\n");
exit(1);
}
2. Match Every malloc() with free()
int* arr = malloc(100 * sizeof(int));
// ... use array ...
free(arr); // REQUIRED!
arr = NULL; // Good practice
3. Don't Access After free()
free(arr);
arr[0] = 5; // ❌ UNDEFINED BEHAVIOR - Don't do this!
4. Don't Double-free
free(arr);
free(arr); // ❌ UNDEFINED BEHAVIOR - Don't do this!
5. Set Pointers to NULL After free()
free(arr);
arr = NULL; // ✅ Good practice - prevents accidental reuse
When to Use Which?
Use Stack Arrays When:
Size is known at compile time
Array is small (< few KB)
Temporary/local use only
Performance is critical
Example:
void process_grades() {
int class_grades[30]; // Known class size
// Process grades...
} // Automatic cleanup
Use Dynamic Arrays When:
Size determined at runtime
Large arrays needed
Array outlives function scope
Need to resize during execution
Example:
int* load_file_data(const char* filename) {
int size = get_file_size(filename);
int* data = malloc(size * sizeof(int));
// Load data...
return data; // Caller responsible for free()
}
Common Errors and Debugging
Memory Leak Example
void memory_leak_bug() {
int* arr = malloc(1000 * sizeof(int));
// ... use array ...
// ❌ Forgot to call free(arr)!
// Memory is now leaked
}
Double Free Example
void double_free_bug() {
int* arr = malloc(10 * sizeof(int));
free(arr);
free(arr); // ❌ CRASH! Double free
}
Use After Free Example
void use_after_free_bug() {
int* arr = malloc(10 * sizeof(int));
free(arr);
arr[0] = 5; // ❌ UNDEFINED BEHAVIOR
}
Understanding these concepts is crucial for C programming and forms the foundation for more
complex data structures like linked lists, trees, and graphs.