Memory Management

Code Properties

  • Language: C

Overview

C provides manual memory management through dynamic allocation functions in <stdlib.h>. Unlike languages with garbage collection, C requires explicit allocation and deallocation, giving developers fine-grained control over memory usage.

Code

malloc - Memory Allocation

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    // allocate memory for a single integer
    int *num = malloc(sizeof(int));
    if (num == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return EXIT_FAILURE;
    }
    
    *num = 42;
    printf("Value: %d\n", *num);
    
    free(num);
    num = NULL;
    
    return EXIT_SUCCESS;
}

Allocating Arrays

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    int n = 5;
    
    // allocate array of n integers
    int *arr = malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return EXIT_FAILURE;
    }
    
    // initialize and use
    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;
    }
    
    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    free(arr);
    arr = NULL;
    
    return EXIT_SUCCESS;
}

calloc - Contiguous Allocation

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    int n = 5;
    
    // calloc initializes all bytes to zero
    int *arr = calloc(n, sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return EXIT_FAILURE;
    }
    
    // all values are 0
    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    free(arr);
    return EXIT_SUCCESS;
}

realloc - Resize Allocation

#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    int capacity = 2;
    int *arr = malloc(capacity * sizeof(int));
    if (arr == NULL) return EXIT_FAILURE;
    
    arr[0] = 10;
    arr[1] = 20;
    
    // expand array
    capacity = 5;
    int *temp = realloc(arr, capacity * sizeof(int));
    if (temp == NULL) {
        free(arr);  // original memory still valid
        return EXIT_FAILURE;
    }
    arr = temp;
    
    arr[2] = 30;
    arr[3] = 40;
    arr[4] = 50;
    
    for (int i = 0; i < capacity; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    free(arr);
    return EXIT_SUCCESS;
}

Dynamic 2D Array

#include <stdio.h>
#include <stdlib.h>
 
int **create_2d_array(int rows, int cols) {
    int **arr = malloc(rows * sizeof(int*));
    if (arr == NULL) return NULL;
    
    for (int i = 0; i < rows; i++) {
        arr[i] = malloc(cols * sizeof(int));
        if (arr[i] == NULL) {
            // cleanup on failure
            for (int j = 0; j < i; j++) {
                free(arr[j]);
            }
            free(arr);
            return NULL;
        }
    }
    return arr;
}
 
void free_2d_array(int **arr, int rows) {
    for (int i = 0; i < rows; i++) {
        free(arr[i]);
    }
    free(arr);
}
 
int main(void) {
    int rows = 3, cols = 4;
    
    int **matrix = create_2d_array(rows, cols);
    if (matrix == NULL) return EXIT_FAILURE;
    
    // initialize
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }
    
    // print
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%3d ", matrix[i][j]);
        }
        printf("\n");
    }
    
    free_2d_array(matrix, rows);
    return EXIT_SUCCESS;
}

Dynamic String

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
char *duplicate_string(const char *src) {
    size_t len = strlen(src) + 1;  // +1 for null terminator
    char *dst = malloc(len);
    if (dst != NULL) {
        memcpy(dst, src, len);
    }
    return dst;
}
 
int main(void) {
    const char *original = "Hello, World!";
    
    char *copy = duplicate_string(original);
    if (copy == NULL) {
        fprintf(stderr, "Failed to duplicate string\n");
        return EXIT_FAILURE;
    }
    
    printf("Original: %s\n", original);
    printf("Copy: %s\n", copy);
    
    free(copy);
    return EXIT_SUCCESS;
}

Details

Memory Functions Reference

FunctionSignatureDescription
mallocvoid *malloc(size_t size)Allocate uninitialized memory
callocvoid *calloc(size_t num, size_t size)Allocate zero-initialized memory
reallocvoid *realloc(void *ptr, size_t size)Resize existing allocation
freevoid free(void *ptr)Deallocate memory

Best Practices

PracticeDescription
Always check for NULLAllocation can fail if memory is exhausted
Free what you allocateEvery malloc/calloc needs a matching free
Set pointer to NULL after freePrevents use-after-free bugs
Use sizeof with typemalloc(n * sizeof(*ptr)) adapts to type changes
Handle realloc carefullyUse temp variable to avoid losing original pointer

Common Memory Bugs

BugDescriptionPrevention
Memory leakAllocated memory never freedTrack allocations, use tools like Valgrind
Double freeCalling free() twice on same pointerSet to NULL after free
Use after freeAccessing memory after it’s freedSet to NULL after free
Buffer overflowWriting beyond allocated boundsCheck bounds, use safe functions
Uninitialized readReading malloc memory before settingUse calloc or initialize explicitly

Memory Debugging Tools

ToolPlatformUsage
ValgrindLinux/macOSvalgrind --leak-check=full ./program
AddressSanitizerGCC/ClangCompile with -fsanitize=address
Dr. MemoryWindows/LinuxSimilar to Valgrind

Appendix

Note created on 2025-12-31 and last modified on 2025-12-31.

See Also


(c) No Clocks, LLC | 2025