Memory Management in C
When a C program is executed, the Operating System allocates a chunk of memory divided into distinct segments:
- TEXT Segment
- Initialized Data Segment
- Uninitialized Data Segment (BSS)
- Stack
- Heap
High Memory Address
┌─────────────────────┐
│ Stack │ ← Grows downward
├─────────────────────┤
│ ↓ │
│ (Free Space) │
│ ↑ │
├─────────────────────┤
│ Heap │ ← Grows upward
├─────────────────────┤
│ Uninitialized Data │ (BSS)
├─────────────────────┤
│ Initialized Data │
├─────────────────────┤
│ TEXT │ (Program Code)
└─────────────────────┘
Low Memory Address
Memory Segment Characteristics
- TEXT, Initialized Data, Uninitialized Data, and Stack have fixed sizes determined at compile time
- Heap size can be dynamically adjusted through system calls during runtime
- Stack and Heap grow toward each other, with free space in between
TEXT Segment
The TEXT segment contains the compiled machine code of your program.
Characteristics:
- Read-only: Prevents accidental modification of program code
- Shared: Multiple instances of the same program can share this segment
- Fixed size: Determined at compile time
Example:
// This function's compiled code goes in TEXT segment
int add(int a, int b) {
return a + b; // Machine instructions stored here
}
Initialized Data Segment
Contains global and static variables that have been explicitly initialized by the programmer.
Characteristics:
- Read-write: Variables can be modified during execution
- Fixed size: Known at compile time
- Persistent: Data persists throughout program execution
Examples:
int global_var = 42; // Goes to initialized data
static int static_var = 100; // Goes to initialized data
char message[] = "Hello"; // Goes to initialized data
int main() {
// These are NOT in initialized data (they're on stack)
int local_var = 10;
return 0;
}
Uninitialized Data Segment (BSS)
Contains global and static variables that have not been initialized or are initialized to zero.
Characteristics:
- Zero-initialized: OS automatically initializes to zero
- Fixed size: Known at compile time
- Memory efficient: Doesn’t store actual zero values in executable
Examples:
int global_array[1000]; // Goes to BSS (initialized to 0)
static int static_counter; // Goes to BSS (initialized to 0)
char buffer[256]; // Goes to BSS (initialized to 0)
int main() {
printf("%d\n", global_array[0]); // Prints 0
return 0;
}
Stack
The stack is used for automatic memory management of local variables and function call management.
Characteristics:
- LIFO (Last In, First Out): Functions are called and return in reverse order
- Limited size: Typically 1-8 MB (system dependent)
- Fast access: Direct memory addressing
- Automatic cleanup: Variables are automatically deallocated when out of scope
What’s Stored on the Stack:
- Primitive data types:
int
,float
,char
,double
- Pointers:
int*
,char*
,void*
- Fixed-size arrays:
int arr[100]
- Fixed-size structs: User-defined structures
- Function parameters and return addresses
- Local variables
Stack Operations:
void function_example() {
int local_int = 10; // Pushed onto stack
char local_array[50]; // Pushed onto stack
int* ptr = &local_int; // Pointer pushed onto stack
// When function returns, all local variables are popped off
}
Stack Overflow:
Occurs when stack size limit is exceeded, typically due to:
- Deep recursion
- Large local arrays
- Excessive function nesting
// This will cause stack overflow
void infinite_recursion() {
int large_array[10000]; // Each call adds to stack
infinite_recursion(); // Never returns, keeps growing stack
}
Heap
The heap provides dynamic memory allocation for data whose size or lifetime cannot be determined at compile time.
Characteristics:
- Virtually unlimited: Can request more memory from OS
- Manual management: Programmer responsible for allocation/deallocation
- Slower access: Requires pointer dereferencing
- Fragmentation: Can become fragmented over time
What’s Stored on the Heap:
- Dynamic arrays: Arrays whose size is determined at runtime
- Linked lists and trees: Dynamic data structures
- Large data structures: When stack space is insufficient
- Any data type: No restrictions on what can be stored
Heap Operations:
#include <stdlib.h>
int main() {
// Allocate memory on heap
int* dynamic_array = malloc(1000 * sizeof(int));
if (dynamic_array == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// Use the memory
for (int i = 0; i < 1000; i++) {
dynamic_array[i] = i * 2;
}
// Free the memory (important!)
free(dynamic_array);
dynamic_array = NULL; // Prevent dangling pointer
return 0;
}
Heap Memory Functions:
malloc(size)
: Allocate memorycalloc(count, size)
: Allocate and zero-initialize memoryrealloc(ptr, new_size)
: Resize previously allocated memoryfree(ptr)
: Deallocate memory
Pointers in C
Pointers are fundamental to understanding memory management in C.
Types of Parameter Passing:
1. Pass by Value (Copy)
void modify_value(int x) {
x = 100; // Only modifies local copy
}
int main() {
int num = 5;
modify_value(num);
printf("%d\n", num); // Still prints 5
return 0;
}
2. Pass by Pointer
void modify_through_pointer(int* x) {
*x = 100; // Modifies original value
}
int main() {
int num = 5;
modify_through_pointer(&num);
printf("%d\n", num); // Prints 100
return 0;
}
3. Pass Pointer to Pointer
void allocate_memory(int** ptr) {
*ptr = malloc(sizeof(int));
**ptr = 42;
}
int main() {
int* my_ptr = NULL;
allocate_memory(&my_ptr);
printf("%d\n", *my_ptr); // Prints 42
free(my_ptr);
return 0;
}
Memory Management Best Practices
Do’s:
- Always check if
malloc()
returns NULL - Free every allocated block of memory
- Set pointers to NULL after freeing
- Use
valgrind
or similar tools to detect memory leaks - Prefer stack allocation when possible (faster and automatic cleanup)
Don’ts:
- Don’t access freed memory
- Don’t free the same memory twice
- Don’t access memory outside allocated bounds
- Don’t ignore compiler warnings about uninitialized variables
Common Memory Errors:
- Memory leaks: Allocated memory never freed
- Dangling pointers: Pointers to freed memory
- Buffer overflows: Writing beyond allocated bounds
- Double free: Freeing same memory twice
- Use after free: Accessing freed memory
Example: Complete Memory Usage
#include <stdio.h>
#include <stdlib.h>
// Global variables (Initialized Data)
int global_counter = 0;
// Global array (BSS - Uninitialized Data)
int global_array[100];
// Function (TEXT segment)
void demonstrate_memory() {
// Local variables (Stack)
int local_var = 10;
char local_buffer[50];
// Dynamic allocation (Heap)
int* heap_array = malloc(200 * sizeof(int));
if (heap_array != NULL) {
// Use heap memory
for (int i = 0; i < 200; i++) {
heap_array[i] = i;
}
// Clean up
free(heap_array);
}
}
int main() {
demonstrate_memory();
return 0;
}