ð c-memory-management
Use when managing memory in C programs with malloc/free, pointers, and avoiding common memory safety pitfalls.
Overview
Master manual memory management in C with proper allocation, deallocation, pointer handling, and techniques to avoid memory leaks and corruption.
Overview
C requires manual memory management through explicit allocation and deallocation. Understanding pointers, the heap, and proper memory handling is crucial for writing safe and efficient C programs.
Installation and Setup
Compiler and Tools
# Install GCC compiler
# macOS
xcode-select --install
# Linux (Ubuntu/Debian)
sudo apt-get install build-essential
# Check installation
gcc --version
# Memory debugging tools
# Install Valgrind (Linux)
sudo apt-get install valgrind
# Install Address Sanitizer (built into GCC/Clang)
gcc -fsanitize=address -g program.c -o program
Compilation Flags
# Basic compilation
gcc program.c -o program
# With warnings and debugging
gcc -Wall -Wextra -g program.c -o program
# With Address Sanitizer
gcc -fsanitize=address -g program.c -o program
# With optimization
gcc -O2 -Wall program.c -o program
Core Patterns
1. Dynamic Memory Allocation
// malloc - allocate memory
#include <stdlib.h>
#include <string.h>
int* allocate_array(size_t size) {
int* arr = malloc(size * sizeof(int));
if (arr == NULL) {
return NULL; // Allocation failed
}
return arr;
}
// calloc - allocate and zero-initialize
int* allocate_zeroed_array(size_t size) {
int* arr = calloc(size, sizeof(int));
if (arr == NULL) {
return NULL;
}
return arr;
}
// realloc - resize allocation
int* resize_array(int* arr, size_t old_size, size_t new_size) {
int* new_arr = realloc(arr, new_size * sizeof(int));
if (new_arr == NULL && new_size > 0) {
// Reallocation failed, original array still valid
return NULL;
}
return new_arr;
}
// free - deallocate memory
void cleanup_array(int** arr) {
if (arr != NULL && *arr != NULL) {
free(*arr);
*arr = NULL; // Prevent dangling pointer
}
}
2. Pointer Basics
// Pointer declaration and usage
void pointer_basics() {
int value = 42;
int* ptr = &value; // ptr points to value
printf("Value: %d\n", value);
printf("Address: %p\n", (void*)&value);
printf("Pointer: %p\n", (void*)ptr);
printf("Dereferenced: %d\n", *ptr);
*ptr = 100; // Modify through pointer
printf("New value: %d\n", value);
}
// Null pointers
void null_pointer_check(int* ptr) {
if (ptr == NULL) {
printf("Null pointer\n");
return;
}
printf("Valid pointer: %d\n", *ptr);
}
// Pointer arithmetic
void pointer_arithmetic() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr;
for (int i = 0; i < 5; i++) {
printf("%d ", *(ptr + i)); // Same as ptr[i]
}
printf("\n");
}
3. Dynamic Strings
#include <string.h>
// Create string copy
char* string_duplicate(const char* str) {
if (str == NULL) {
return NULL;
}
size_t len = strlen(str);
char* copy = malloc(len + 1); // +1 for null terminator
if (copy != NULL) {
strcpy(copy, str);
}
return copy;
}
// String concatenation
char* string_concat(const char* s1, const char* s2) {
if (s1 == NULL || s2 == NULL) {
return NULL;
}
size_t len1 = strlen(s1);
size_t len2 = strlen(s2);
char* result = malloc(len1 + len2 + 1);
if (result == NULL) {
return NULL;
}
strcpy(result, s1);
strcat(result, s2);
return result;
}
// Safe string functions
char* safe_string_copy(const char* src, size_t max_len) {
if (src == NULL) {
return NULL;
}
size_t len = strnlen(src, max_len);
char* dest = malloc(len + 1);
if (dest != NULL) {
memcpy(dest, src, len);
dest[len] = '\0';
}
return dest;
}
4. Structures and Memory
// Structure with dynamic members
typedef struct {
char* name;
int* scores;
size_t num_scores;
} Student;
Student* create_student(const char* name, size_t num_scores) {
Student* student = malloc(sizeof(Student));
if (student == NULL) {
return NULL;
}
student->name = string_duplicate(name);
if (student->name == NULL) {
free(student);
return NULL;
}
student->scores = malloc(num_scores * sizeof(int));
if (student->scores == NULL) {
free(student->name);
free(student);
return NULL;
}
student->num_scores = num_scores;
memset(student->scores, 0, num_scores * sizeof(int));
return student;
}
void destroy_student(Student** student) {
if (student == NULL || *student == NULL) {
return;
}
free((*student)->name);
free((*student)->scores);
free(*student);
*student = NULL;
}
5. Memory Pools
// Simple memory pool
typedef struct {
void* pool;
size_t block_size;
size_t num_blocks;
size_t next_free;
} MemoryPool;
MemoryPool* create_pool(size_t block_size, size_t num_blocks) {
MemoryPool* pool = malloc(sizeof(MemoryPool));
if (pool == NULL) {
return NULL;
}
pool->pool = malloc(block_size * num_blocks);
if (pool->pool == NULL) {
free(pool);
return NULL;
}
pool->block_size = block_size;
pool->num_blocks = num_blocks;
pool->next_free = 0;
return pool;
}
void* pool_allocate(MemoryPool* pool) {
if (pool == NULL || pool->next_free >= pool->num_blocks) {
return NULL;
}
void* block = (char*)pool->pool + (pool->next_free * pool->block_size);
pool->next_free++;
return block;
}
void destroy_pool(MemoryPool** pool) {
if (pool == NULL || *pool == NULL) {
return;
}
free((*pool)->pool);
free(*pool);
*pool = NULL;
}
6. Reference Counting
// Reference counted string
typedef struct {
char* data;
size_t ref_count;
} RefString;
RefString* refstring_create(const char* str) {
RefString* rs = malloc(sizeof(RefString));
if (rs == NULL) {
return NULL;
}
rs->data = string_duplicate(str);
if (rs->data == NULL) {
free(rs);
return NULL;
}
rs->ref_count = 1;
return rs;
}
RefString* refstring_retain(RefString* rs) {
if (rs != NULL) {
rs->ref_count++;
}
return rs;
}
void refstring_release(RefString** rs) {
if (rs == NULL || *rs == NULL) {
return;
}
(*rs)->ref_count--;
if ((*rs)->ref_count == 0) {
free((*rs)->data);
free(*rs);
}
*rs = NULL;
}
7. Memory Debugging
// Debug wrapper for malloc
#ifdef DEBUG_MEMORY
typedef struct {
void* ptr;
size_t size;
const char* file;
int line;
} AllocationInfo;
static AllocationInfo allocations[1000];
static size_t num_allocations = 0;
void* debug_malloc(size_t size, const char* file, int line) {
void* ptr = malloc(size);
if (ptr != NULL && num_allocations < 1000) {
allocations[num_allocations].ptr = ptr;
allocations[num_allocations].size = size;
allocations[num_allocations].file = file;
allocations[num_allocations].line = line;
num_allocations++;
}
return ptr;
}
void debug_free(void* ptr) {
for (size_t i = 0; i < num_allocations; i++) {
if (allocations[i].ptr == ptr) {
allocations[i] = allocations[num_allocations - 1];
num_allocations--;
break;
}
}
free(ptr);
}
void print_leaks() {
printf("Memory leaks: %zu\n", num_allocations);
for (size_t i = 0; i < num_allocations; i++) {
printf(" %p (%zu bytes) at %s:%d\n",
allocations[i].ptr,
allocations[i].size,
allocations[i].file,
allocations[i].line);
}
}
#define malloc(size) debug_malloc(size, __FILE__, __LINE__)
#define free(ptr) debug_free(ptr)
#endif
8. Stack vs Heap
// Stack allocation
void stack_example() {
int local_var = 42; // Stack
char buffer[100]; // Stack
// Automatically freed when function returns
}
// Heap allocation
void heap_example() {
int* dynamic = malloc(sizeof(int)); // Heap
if (dynamic != NULL) {
*dynamic = 42;
free(dynamic); // Must manually free
}
}
// Mixed allocation
typedef struct {
int id; // Stack (part of struct)
char* name; // Heap (pointer to heap)
} Record;
Record* create_record(int id, const char* name) {
Record* rec = malloc(sizeof(Record)); // Heap
if (rec == NULL) {
return NULL;
}
rec->id = id; // Stack value
rec->name = string_duplicate(name); // Heap
if (rec->name == NULL) {
free(rec);
return NULL;
}
return rec;
}
9. Double Free Prevention
// Safe free macro
#define SAFE_FREE(ptr) do { \
if (ptr != NULL) { \
free(ptr); \
ptr = NULL; \
} \
} while(0)
// Usage
void safe_cleanup() {
int* arr = malloc(10 * sizeof(int));
// ... use arr ...
SAFE_FREE(arr);
// arr is now NULL, can safely call again
SAFE_FREE(arr); // No-op, safe
}
// Reference clearing
void clear_reference(void** ref) {
if (ref != NULL && *ref != NULL) {
free(*ref);
*ref = NULL;
}
}
10. Memory Alignment
#include <stdalign.h>
// Aligned allocation
void* aligned_malloc(size_t size, size_t alignment) {
void* ptr = NULL;
#ifdef _WIN32
ptr = _aligned_malloc(size, alignment);
#else
if (posix_memalign(&ptr, alignment, size) != 0) {
return NULL;
}
#endif
return ptr;
}
void aligned_free(void* ptr) {
#ifdef _WIN32
_aligned_free(ptr);
#else
free(ptr);
#endif
}
// Structure alignment
typedef struct {
alignas(16) double values[4]; // 16-byte aligned
} AlignedData;
Best Practices
- Always check malloc return value - Handle allocation failures
- Free all allocated memory - Prevent memory leaks
- Set pointers to NULL after free - Avoid dangling pointers
- Use sizeof with types - Ensure correct allocation size
- Initialize allocated memory - Use calloc or memset
- Match malloc/free calls - Every allocation needs deallocation
- Use valgrind for testing - Detect memory errors
- Avoid manual pointer arithmetic - Use array indexing when possible
- Handle realloc failures - Keep original pointer valid
- Document ownership - Clarify who frees memory
Common Pitfalls
- Memory leaks - Forgetting to free allocated memory
- Double free - Freeing same pointer twice
- Use after free - Accessing freed memory
- Buffer overflow - Writing beyond allocated bounds
- Dangling pointers - Using pointers after free
- Null pointer dereference - Not checking for NULL
- sizeof mistakes - Using wrong size calculations
- Stack overflow - Large stack allocations
- Uninitialized memory - Reading uninitialized data
- Memory fragmentation - Poor allocation patterns
When to Use
- Systems programming requiring manual control
- Embedded systems with limited resources
- Performance-critical applications
- Operating system development
- Device drivers and kernel modules
- Real-time systems
- Legacy codebase maintenance
- Interfacing with hardware
- Memory-constrained environments
- Low-level library development