DevAcademia
C++C#CPythonJava
  • C Basics

  • Introduction to C
  • Getting Started with C
  • C Syntax
  • C Output
  • C Comments
  • C Variables
  • C Data Types
  • C Constants
  • C Operators
  • C Booleans
  • C If...Else Statements
  • C Switch Statement
  • C While Loops
  • C For Loops
  • C Break and Continue
  • C Strings
  • C User Input
  • C Memory Address
  • C Pointers
  • C Files
  • C Functions

  • C Functions
  • C Function Parameters
  • C Scope
  • C Function Declaration
  • C Recursion
  • C Math Functions
  • C Structures

  • C Structures
  • C Structs & Pointers
  • C Unions
  • C Enums

  • C Enums
  • C Memory

  • C Allocate Memory
  • C Access Memory
  • C Reallocate Memory
  • C Deallocate Memory
  • C Structs and Memory
  • C Memory Example
  • C Quiz

  • C Quiz
  • C Basics

  • Introduction to C
  • Getting Started with C
  • C Syntax
  • C Output
  • C Comments
  • C Variables
  • C Data Types
  • C Constants
  • C Operators
  • C Booleans
  • C If...Else Statements
  • C Switch Statement
  • C While Loops
  • C For Loops
  • C Break and Continue
  • C Strings
  • C User Input
  • C Memory Address
  • C Pointers
  • C Files
  • C Functions

  • C Functions
  • C Function Parameters
  • C Scope
  • C Function Declaration
  • C Recursion
  • C Math Functions
  • C Structures

  • C Structures
  • C Structs & Pointers
  • C Unions
  • C Enums

  • C Enums
  • C Memory

  • C Allocate Memory
  • C Access Memory
  • C Reallocate Memory
  • C Deallocate Memory
  • C Structs and Memory
  • C Memory Example
  • C Quiz

  • C Quiz

Loading C tutorial…

Loading content
C MemoryTopic 63 of 64
←PreviousPrevNextNext→

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

Example
#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;
}
Output
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

Example
// 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;
}
Output
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

Example
// 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;
}
Output
Found student with ID 1001 at index 0

Proper Cleanup and Error Handling

Example
// 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);
}
Output
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.

Test your knowledge: C Memory Example
Quiz Configuration
8 of 8 questions
Sequential
Previous allowed
Review enabled
Early close allowed
Estimated time: 10 min
C MemoryTopic 63 of 64
←PreviousPrevNextNext→