C Memory Example
Introduction to Practical Memory Management
This comprehensive example demonstrates real-world memory management scenarios in C programming. We'll explore building a complete program that manages a dynamic list of students with various memory operations including allocation, reallocation, accessing, and proper cleanup.
This example covers:
- Dynamic memory allocation for complex data structures
- Memory reallocation for growing data sets
- Safe memory access and bounds checking
- Proper error handling and cleanup
- Memory leak prevention strategies
- Performance considerations in memory management
Complete Student Management System
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INITIAL_CAPACITY 2
#define MAX_SUBJECT_LENGTH 30
struct Student {
int id;
char *name; // dynamically allocated
float *grades; // dynamically allocated, grows with realloc
int numGrades;
int gradeCapacity;
char subject[MAX_SUBJECT_LENGTH]; // fixed-size buffer
};
struct StudentList {
struct Student *students; // dynamically allocated array of Students
int count;
int capacity;
};
static void freeStudent(struct Student *student) {
if (!student) return;
printf("Freeing memory for student: %s\n", student->name ? student->name : "Unknown");
free(student->name);
free(student->grades);
student->name = NULL;
student->grades = NULL;
student->numGrades = 0;
student->gradeCapacity = 0;
}
static struct StudentList *createStudentList(void) {
struct StudentList *list = malloc(sizeof *list);
if (!list) {
printf("Failed to allocate memory for student list\n");
return NULL;
}
// calloc so embedded pointers start as NULL and ints as 0
list->students = calloc(INITIAL_CAPACITY, sizeof *list->students);
if (!list->students) {
printf("Failed to allocate memory for students array\n");
free(list);
return NULL;
}
list->count = 0;
list->capacity = INITIAL_CAPACITY;
printf("Student list created with capacity: %d\n", list->capacity);
printf("Initial memory usage: %zu bytes\n",
sizeof(struct StudentList) + (size_t)list->capacity * sizeof(struct Student));
return list;
}
static int ensureListCapacity(struct StudentList *list) {
if (list->count < list->capacity) return 1;
int newCap = (list->capacity > 0) ? list->capacity * 2 : INITIAL_CAPACITY;
struct Student *newBlock = realloc(list->students, (size_t)newCap * sizeof *newBlock);
if (!newBlock) {
printf("Failed to grow students array to %d\n", newCap);
return 0; // keep old block intact
}
// zero-init the newly added tail so pointers start NULL
memset(newBlock + list->capacity, 0, (size_t)(newCap - list->capacity) * sizeof *newBlock);
list->students = newBlock;
list->capacity = newCap;
printf("Resized students array to capacity: %d\n", newCap);
return 1;
}
static int addStudent(struct StudentList *list, int id, const char *name, const char *subject) {
if (!list) return 0;
if (!ensureListCapacity(list)) return 0;
struct Student *s = &list->students[list->count];
s->id = id;
// allocate and copy name
size_t nlen = name ? strlen(name) : 0;
s->name = malloc(nlen + 1);
if (!s->name) {
printf("Failed to allocate name for student id=%d\n", id);
// leave struct in zeroed/valid state
return 0;
}
memcpy(s->name, name ? name : "", nlen + 1);
// subject: copy into fixed buffer safely
if (subject) {
strncpy(s->subject, subject, MAX_SUBJECT_LENGTH - 1);
s->subject[MAX_SUBJECT_LENGTH - 1] = '\0';
} else {
s->subject[0] = '\0';
}
s->grades = NULL; // lazily allocated when first grade is added
s->numGrades = 0;
s->gradeCapacity = 0;
list->count++;
printf("Added student %s (id=%d) [count=%d, capacity=%d]\n",
s->name, s->id, list->count, list->capacity);
return 1;
}
static struct Student *findStudentById(struct StudentList *list, int id) {
if (!list || !list->students) {
printf("Invalid student list\n");
return NULL;
}
for (int i = 0; i < list->count; i++) {
if (list->students[i].id == id) {
printf("Found student with ID %d at index %d\n", id, i);
return &list->students[i];
}
}
printf("Student with ID %d not found\n", id);
return NULL;
}
static int addGradeToStudent(struct Student *student, float grade) {
if (!student) return 0;
if (student->numGrades >= student->gradeCapacity) {
int newCap = (student->gradeCapacity > 0) ? student->gradeCapacity * 2 : 2; // start small, then double
float *newGrades = realloc(student->grades, (size_t)newCap * sizeof *newGrades);
if (!newGrades) {
printf("Failed to resize grades array for student %s\n", student->name ? student->name : "(null)");
return 0;
}
student->grades = newGrades;
student->gradeCapacity = newCap;
printf("Resized grades array for %s to capacity: %d\n", student->name, newCap);
}
student->grades[student->numGrades++] = grade;
printf("Added grade %.1f to %s (Total grades: %d)\n", grade, student->name, student->numGrades);
return 1;
}
static void freeStudentList(struct StudentList *list) {
if (!list) return;
for (int i = 0; i < list->count; i++) {
freeStudent(&list->students[i]);
}
free(list->students);
free(list);
}
int main(void) {
struct StudentList *list = createStudentList();
if (!list) return 1;
addStudent(list, 1001, "Alice Johnson", "Mathematics");
addStudent(list, 1002, "Bob Smith", "Physics");
struct Student *alice = findStudentById(list, 1001);
if (alice) {
addGradeToStudent(alice, 95.5f);
addGradeToStudent(alice, 87.0f);
addGradeToStudent(alice, 91.0f); // triggers resize from 2 -> 4
}
freeStudentList(list);
printf("All memory successfully freed\n");
return 0;
}
Student list created with capacity: 2 Initial memory usage: (varies by platform) bytes Added student Alice Johnson (id=1001) [count=1, capacity=2] Added student Bob Smith (id=1002) [count=2, capacity=2] Found student with ID 1001 at index 0 Resized grades array for Alice Johnson to capacity: 2 Added grade 95.5 to Alice Johnson (Total grades: 1) Added grade 87.0 to Alice Johnson (Total grades: 2) Resized grades array for Alice Johnson to capacity: 4 Added grade 91.0 to Alice Johnson (Total grades: 3) Freeing memory for student: Alice Johnson Freeing memory for student: Bob Smith All memory successfully freed
Managing Grades with Dynamic Memory
// Function to add a grade to a student (with lazy allocation and reallocation)
int addGradeToStudent(struct Student *student, float grade) {
if (student == NULL) {
return 0;
}
if (student->numGrades >= student->gradeCapacity) {
int newCapacity = (student->gradeCapacity > 0) ? student->gradeCapacity * 2 : 2; // start at 2
float *newGrades = realloc(student->grades, (size_t)newCapacity * sizeof *newGrades);
if (newGrades == NULL) {
printf("Failed to resize grades array for student %s\n", student->name ? student->name : "(null)");
return 0; // keep old pointer valid
}
student->grades = newGrades;
student->gradeCapacity = newCapacity;
printf("Resized grades array for %s to capacity: %d\n", student->name, newCapacity);
}
student->grades[student->numGrades] = grade;
student->numGrades++;
printf("Added grade %.1f to %s (Total grades: %d)\n", grade, student->name, student->numGrades);
return 1;
}
Resized grades array for Alice Johnson to capacity: 2 Added grade 95.5 to Alice Johnson (Total grades: 1) Added grade 87.0 to Alice Johnson (Total grades: 2) Resized grades array for Alice Johnson to capacity: 4 Added grade 91.0 to Alice Johnson (Total grades: 3)
Memory Access and Safety Functions
// Safe function to find student by ID
struct Student* findStudentById(struct StudentList *list, int id) {
if (list == NULL || list->students == NULL) {
printf("Invalid student list\n");
return NULL;
}
for (int i = 0; i < list->count; i++) {
if (list->students[i].id == id) {
printf("Found student with ID %d at index %d\n", id, i);
return &list->students[i];
}
}
printf("Student with ID %d not found\n", id);
return NULL;
}
Found student with ID 1001 at index 0
Proper Cleanup and Error Handling
// Function to free a single student's memory and a helper to free the whole list
void freeStudent(struct Student *student) {
if (student != NULL) {
printf("Freeing memory for student: %s\n", student->name ? student->name : "Unknown");
free(student->name);
free(student->grades);
student->name = NULL;
student->grades = NULL;
student->numGrades = 0;
student->gradeCapacity = 0;
}
}
void freeStudentList(struct StudentList *list) {
if (!list) return;
for (int i = 0; i < list->count; i++) {
freeStudent(&list->students[i]);
}
free(list->students);
free(list);
}
Freeing memory for student: Alice Johnson Freeing memory for student: Bob Smith All memory successfully freed
Key Lessons from This Example
- Always check for NULL returns from memory allocation functions
- Use realloc() to resize dynamic arrays when they reach capacity
- Implement bounds checking for all array access operations
- Free memory in the reverse order of allocation (dynamic members first)
- Set freed pointers to NULL to prevent accidental reuse
- Use defensive programming with input validation
- Track memory usage to identify potential optimization opportunities
- Implement proper error handling and cleanup paths
- Use tools like valgrind or AddressSanitizer to detect memory leaks and errors
Memory Management Best Practices Demonstrated
This comprehensive example demonstrates several critical memory management practices:
1. **Allocation Strategy**: Start with a small initial capacity and grow as needed using realloc().
2. **Error Handling**: Always check allocation returns and provide cleanup paths for partial failures.
3. **Memory Tracking**: Monitor memory usage and efficiency to optimize performance.
4. **Safe Access**: Implement bounds checking and validation for all memory operations.
5. **Proper Cleanup**: Free memory in correct order and set pointers to NULL after freeing.
6. **Structured Cleanup**: Use a single-exit or `goto cleanup` pattern to ensure every allocation is freed on all error paths.
This example shows how real-world C programs manage complex data structures while maintaining memory safety and efficiency.