C Language Memory Management: Key Concepts and Examples
Introduction
Memory management in C is critical for efficient and bug-free programs. C requires manual memory handling, which offers control but demands care to avoid leaks and crashes. This guide focuses on practical code examples to demonstrate memory management techniques.
Memory Segments in C
- Stack: Local variables, auto-managed.
- Heap: Dynamic memory, manually managed.
- Static/Global: Persistent variables.
- Code/Text: Program instructions.
Dynamic Memory Allocation Examples
1. Using malloc()
Allocate memory for an array of 5 integers.
#include <stdlib.h>
#include <stdio.h>
int main() {
int *arr = (int *)malloc(5 * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
printf("%d ", arr[i]);
}
free(arr);
arr = NULL;
return 0;
}
2. Using calloc()
Allocate and initialize memory to zero for 3 doubles.
#include <stdlib.h>
#include <stdio.h>
int main() {
double *values = (double *)calloc(3, sizeof(double));
if (values == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
for (int i = 0; i < 3; i++) {
printf("%.1f ", values[i]); // Prints 0.0
}
free(values);
values = NULL;
return 0;
}
3. Using realloc()
Resize an existing array to hold 10 integers.
#include <stdlib.h>
#include <stdio.h>
int main() {
int *arr = (int *)malloc(5 * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
arr = (int *)realloc(arr, 10 * sizeof(int));
if (arr == NULL) {
printf("Reallocation failed!\n");
return 1;
}
for (int i = 5; i < 10; i++) {
arr[i] = i + 1;
}
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
free(arr);
arr = NULL;
return 0;
}
Handling Common Issues
Avoiding Memory Leaks
Always free allocated memory.
#include <stdlib.h>
#include <stdio.h>
int main() {
int *ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
*ptr = 42;
printf("Value: %d\n", *ptr);
free(ptr); // Prevents memory leak
ptr = NULL; // Avoids dangling pointer
return 0;
}
Preventing Dangling Pointers
Set pointers to NULL
after freeing.
#include <stdlib.h>
#include <stdio.h>
int main() {
char *str = (char *)malloc(10 * sizeof(char));
if (str == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
strcpy(str, "Hello");
printf("%s\n", str);
free(str);
str = NULL; // Prevents accidental use
if (str == NULL) {
printf("Pointer is NULL, safe!\n");
}
return 0;
}
Avoiding Segmentation Faults
Check for valid memory access.
#include <stdlib.h>
#include <stdio.h>
int main() {
int *arr = (int *)malloc(3 * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
for (int i = 0; i < 3; i++) {
arr[i] = i;
printf("%d ", arr[i]);
}
// Avoid accessing arr[3] to prevent segfault
free(arr);
arr = NULL;
return 0;
}
Best Practices with Code
Modular Memory Management
Encapsulate allocation/deallocation in functions.
#include <stdlib.h>
#include <stdio.h>
int *create_array(int size) {
int *arr = (int *)malloc(size * sizeof(int));
if (arr == NULL) {
return NULL;
}
for (int i = 0; i < size; i++) {
arr[i] = i;
}
return arr;
}
void free_array(int *arr) {
if (arr != NULL) {
free(arr);
}
}
int main() {
int *arr = create_array(5);
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
free_array(arr);
return 0;
}
Memory Pool Example
Simple memory pool for fixed-size blocks.
#include <stdlib.h>
#include <stdio.h>
#define BLOCK_SIZE 32
#define POOL_SIZE 10
typedef struct {
char pool[POOL_SIZE][BLOCK_SIZE];
int used[POOL_SIZE];
} MemoryPool;
void init_pool(MemoryPool *pool) {
for (int i = 0; i < POOL_SIZE; i++) {
pool->used[i] = 0;
}
}
char *alloc_block(MemoryPool *pool) {
for (int i = 0; i < POOL_SIZE; i++) {
if (!pool->used[i]) {
pool->used[i] = 1;
return pool->pool[i];
}
}
return NULL;
}
void free_block(MemoryPool *pool, char *block) {
for (int i = 0; i < POOL_SIZE; i++) {
if (pool->pool[i] == block) {
pool->used[i] = 0;
break;
}
}
}
int main() {
MemoryPool pool;
init_pool(&pool);
char *block1 = alloc_block(&pool);
if (block1) {
strcpy(block1, "Test");
printf("%s\n", block1);
}
free_block(&pool, block1);
return 0;
}
Debugging Tools
- Valgrind: Run
valgrind --leak-check=full ./program
to detect leaks. - AddressSanitizer: Compile with
-fsanitize=address
for runtime checks. - GDB: Use
gdb ./program
to debug segfaults.
Conclusion
Effective memory management in C requires careful use of allocation functions, error checking, and cleanup. The examples above demonstrate practical techniques to allocate, manage, and free memory while avoiding common issues like leaks and segfaults. Practice these patterns to write robust C programs.
Call to Action: Try these examples in your projects and share your tips in the comments!