Dynamic Memory (malloc/free)

Managing memory allocation and deallocation at runtime

๐Ÿ”„ Dynamic Memory Management

Dynamic memory allows runtime allocation and deallocation using malloc() and free(). This provides flexible memory usage for varying data sizes.


// Allocate memory for 10 integers
int *ptr = malloc(10 * sizeof(int));
// Use the memory...
free(ptr);  // Release memory when done
                                    

Memory Management Functions

๐Ÿ—๏ธ

malloc()

Allocate memory block

ptr = malloc(size);
๐Ÿงน

free()

Deallocate memory block

free(ptr);
๐Ÿ”„

realloc()

Resize memory block

ptr = realloc(ptr, new_size);
๐Ÿงฝ

calloc()

Allocate and initialize to zero

ptr = calloc(count, size);

๐Ÿ”น Basic malloc() and free() Usage

malloc() dynamically allocates memory at runtime, and free() releases it. Dynamic memory allocation allows programs to request memory based on runtime needs rather than fixed amounts. The malloc() function returns a pointer to allocated memory, which you must later release with free() to prevent memory leaks. Proper memory management is crucial for efficient programs that handle varying data sizes and prevent resource depletion.

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n, i;
    int *numbers;
    
    printf("Enter number of elements: ");
    scanf("%d", &n);
    
    // Allocate memory for n integers
    numbers = malloc(n * sizeof(int));
    
    // Check if allocation was successful
    if (numbers == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }
    
    // Input values
    printf("Enter %d numbers:\n", n);
    for (i = 0; i < n; i++) {
        scanf("%d", &numbers[i]);
    }
    
    // Display values
    printf("You entered: ");
    for (i = 0; i < n; i++) {
        printf("%d ", numbers[i]);
    }
    
    // Free allocated memory
    free(numbers);
    numbers = NULL;  // Good practice
    
    return 0;
}

Output:

Enter number of elements: 5
Enter 5 numbers:
10 20 30 40 50
You entered: 10 20 30 40 50

๐Ÿ”น calloc() vs malloc()

calloc() allocates memory and initializes it to zero, while malloc() allocates uninitialized memory. The calloc() function takes two arguments: number of elements and size per element, calculating total bytes automatically. malloc() takes only the total bytes needed. Choose calloc() when you need zeroed memory; use malloc() for faster allocation when initialization isn't necessary. Understanding these differences optimizes memory management in your programs.

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *malloc_array, *calloc_array;
    int size = 5;
    
    // malloc - uninitialized memory
    malloc_array = malloc(size * sizeof(int));
    
    // calloc - initialized to zero
    calloc_array = calloc(size, sizeof(int));
    
    printf("malloc array (uninitialized): ");
    for (int i = 0; i < size; i++) {
        printf("%d ", malloc_array[i]);
    }
    
    printf("\ncalloc array (zero-initialized): ");
    for (int i = 0; i < size; i++) {
        printf("%d ", calloc_array[i]);
    }
    
    // Free memory
    free(malloc_array);
    free(calloc_array);
    
    return 0;
}

Output:

malloc array (uninitialized): 32767 0 4196352 0 4196432 
calloc array (zero-initialized): 0 0 0 0 0

๐Ÿ”น realloc() for Resizing Memory

The realloc() function dynamically resizes previously allocated memory blocks. When your data grows beyond allocated space, realloc() adjusts memory size without losing existing data. This function copies data to new memory if resizing in place isn't possible. Using realloc() enables flexible data structures that grow or shrink during program execution, providing dynamic solutions for variable-sized collections.

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *numbers;
    int initial_size = 3;
    int new_size = 6;
    
    // Initial allocation
    numbers = malloc(initial_size * sizeof(int));
    
    // Initialize first 3 elements
    for (int i = 0; i < initial_size; i++) {
        numbers[i] = (i + 1) * 10;
    }
    
    printf("Initial array: ");
    for (int i = 0; i < initial_size; i++) {
        printf("%d ", numbers[i]);
    }
    
    // Resize the array
    numbers = realloc(numbers, new_size * sizeof(int));
    
    // Initialize new elements
    for (int i = initial_size; i < new_size; i++) {
        numbers[i] = (i + 1) * 10;
    }
    
    printf("\nResized array: ");
    for (int i = 0; i < new_size; i++) {
        printf("%d ", numbers[i]);
    }
    
    free(numbers);
    return 0;
}

Output:

Initial array: 10 20 30 
Resized array: 10 20 30 40 50 60

๐Ÿ”น Safe Memory Management

Implement best practices to prevent memory leaks and segmentation faults. Always check if malloc() returns NULL before using the pointer, indicating allocation failure. Match every malloc() with a corresponding free(), and avoid using pointers after freeing memory. Initialize pointers to NULL and set them to NULL after freeing. These practices protect your programs from crashes and ensure stable, reliable execution with proper resource handling.

โœ… Safe Memory Practices

// Always check malloc return value
int *ptr = malloc(size * sizeof(int));
if (ptr == NULL) {
    printf("Memory allocation failed!\n");
    exit(1);
}

// Use the memory...

// Free and nullify pointer
free(ptr);
ptr = NULL;  // Prevents accidental reuse

โŒ Dangerous Practices

// Don't do this!
int *ptr = malloc(100 * sizeof(int));
// ... use ptr ...
free(ptr);
*ptr = 10;  // Use after free - undefined behavior!

// Or this!
free(ptr);
free(ptr);  // Double free - program crash!

๐Ÿง  Test Your Knowledge

What should you do after calling free() on a pointer?