c

examples

examples.c🔧
/**
 * =============================================================================
 * INTRODUCTION TO DYNAMIC MEMORY ALLOCATION: EXAMPLES
 * =============================================================================
 * 
 * This file demonstrates fundamental concepts of dynamic memory allocation in C.
 * 
 * Topics covered:
 * - Static vs dynamic memory comparison
 * - Memory layout visualization
 * - Basic malloc, calloc, realloc, free usage
 * - Heap behavior demonstration
 * - Common patterns and idioms
 * - Error handling for allocations
 * 
 * Compile: gcc examples.c -o examples
 * Run: ./examples
 * =============================================================================
 */

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

/**
 * =============================================================================
 * EXAMPLE 1: Static vs Dynamic Memory
 * =============================================================================
 * Demonstrates the difference between stack and heap allocation.
 */
void example1_static_vs_dynamic(void) {
    printf("=== EXAMPLE 1: Static vs Dynamic Memory ===\n\n");
    
    // -------------------------------------------------------------------------
    // Static allocation (stack)
    // -------------------------------------------------------------------------
    printf("--- Static Allocation (Stack) ---\n");
    
    int staticArray[5] = {1, 2, 3, 4, 5};  // Size must be known at compile time
    
    printf("Static array: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", staticArray[i]);
    }
    printf("\n");
    printf("Address: %p (on stack)\n", (void*)staticArray);
    printf("Size: 5 elements (fixed at compile time)\n\n");
    
    // -------------------------------------------------------------------------
    // Dynamic allocation (heap)
    // -------------------------------------------------------------------------
    printf("--- Dynamic Allocation (Heap) ---\n");
    
    int size = 5;  // Size can be determined at runtime
    int *dynamicArray = (int*)malloc(size * sizeof(int));
    
    if (dynamicArray == NULL) {
        printf("Memory allocation failed!\n");
        return;
    }
    
    // Initialize the dynamic array
    for (int i = 0; i < size; i++) {
        dynamicArray[i] = (i + 1) * 10;
    }
    
    printf("Dynamic array: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", dynamicArray[i]);
    }
    printf("\n");
    printf("Address: %p (on heap)\n", (void*)dynamicArray);
    printf("Size: %d elements (determined at runtime)\n", size);
    
    // Free dynamically allocated memory
    free(dynamicArray);
    dynamicArray = NULL;  // Good practice
    
    printf("\nKey Difference:\n");
    printf("  Static: Size fixed at compile time, automatic cleanup\n");
    printf("  Dynamic: Size flexible at runtime, manual cleanup required\n");
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 2: Memory Layout Visualization
 * =============================================================================
 * Shows where different types of variables are stored in memory.
 */

// Global variables (data segment)
int globalInitialized = 100;    // Initialized data segment
int globalUninitialized;        // BSS segment (zero-initialized)

void example2_memory_layout(void) {
    printf("=== EXAMPLE 2: Memory Layout Visualization ===\n\n");
    
    // Static variable (data segment)
    static int staticVar = 200;
    
    // Local variable (stack)
    int localVar = 300;
    
    // Dynamic memory (heap)
    int *heapVar = (int*)malloc(sizeof(int));
    *heapVar = 400;
    
    // Function pointer (to show code segment)
    void (*funcPtr)(void) = example2_memory_layout;
    
    printf("Memory Regions (approximate locations):\n\n");
    
    printf("Code Segment (Low Address):\n");
    printf("  Function address: %p\n", (void*)funcPtr);
    printf("\n");
    
    printf("Data Segment (Initialized):\n");
    printf("  globalInitialized: %p, value = %d\n", 
           (void*)&globalInitialized, globalInitialized);
    printf("  staticVar:         %p, value = %d\n", 
           (void*)&staticVar, staticVar);
    printf("\n");
    
    printf("BSS Segment (Uninitialized):\n");
    printf("  globalUninitialized: %p, value = %d\n", 
           (void*)&globalUninitialized, globalUninitialized);
    printf("\n");
    
    printf("Heap (Dynamic):\n");
    printf("  heapVar points to: %p, value = %d\n", 
           (void*)heapVar, *heapVar);
    printf("\n");
    
    printf("Stack (Local):\n");
    printf("  localVar:  %p, value = %d\n", 
           (void*)&localVar, localVar);
    printf("  heapVar:   %p (pointer itself on stack)\n", 
           (void*)&heapVar);
    printf("\n");
    
    printf("Typical Memory Layout (Low to High):\n");
    printf("  +------------------+\n");
    printf("  | Text (Code)      | <- Lowest\n");
    printf("  +------------------+\n");
    printf("  | Data (Init)      |\n");
    printf("  +------------------+\n");
    printf("  | BSS (Uninit)     |\n");
    printf("  +------------------+\n");
    printf("  | Heap   ↓         | <- Grows upward\n");
    printf("  |        ...       |\n");
    printf("  | Stack  ↑         | <- Grows downward\n");
    printf("  +------------------+ <- Highest\n");
    printf("\n");
    
    free(heapVar);
}

/**
 * =============================================================================
 * EXAMPLE 3: Basic malloc() Usage
 * =============================================================================
 * Demonstrates various ways to use malloc().
 */
void example3_malloc_basics(void) {
    printf("=== EXAMPLE 3: Basic malloc() Usage ===\n\n");
    
    // -------------------------------------------------------------------------
    // Allocating a single integer
    // -------------------------------------------------------------------------
    printf("--- Allocating Single Integer ---\n");
    
    int *singleInt = (int*)malloc(sizeof(int));
    if (singleInt == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    *singleInt = 42;
    printf("Allocated integer: %d at %p\n", *singleInt, (void*)singleInt);
    printf("Size requested: %zu bytes\n", sizeof(int));
    
    free(singleInt);
    printf("Memory freed.\n\n");
    
    // -------------------------------------------------------------------------
    // Allocating an array
    // -------------------------------------------------------------------------
    printf("--- Allocating Integer Array ---\n");
    
    int n = 10;
    int *array = (int*)malloc(n * sizeof(int));
    
    if (array == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    printf("Allocated array of %d integers at %p\n", n, (void*)array);
    printf("Total size: %zu bytes\n", n * sizeof(int));
    
    // Initialize with values
    for (int i = 0; i < n; i++) {
        array[i] = i * i;  // Squares
    }
    
    printf("Array values: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    
    free(array);
    printf("Array freed.\n\n");
    
    // -------------------------------------------------------------------------
    // Allocating a string
    // -------------------------------------------------------------------------
    printf("--- Allocating String ---\n");
    
    const char *original = "Hello, Dynamic World!";
    size_t len = strlen(original) + 1;  // +1 for null terminator
    
    char *str = (char*)malloc(len);
    if (str == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    strcpy(str, original);
    printf("Allocated string: \"%s\"\n", str);
    printf("String length: %zu (including null terminator)\n", len);
    printf("Address: %p\n", (void*)str);
    
    free(str);
    printf("String freed.\n\n");
    
    // -------------------------------------------------------------------------
    // Best practice: sizeof with variable
    // -------------------------------------------------------------------------
    printf("--- Best Practice: sizeof(*pointer) ---\n");
    
    double *d = malloc(sizeof(*d));  // Type only in declaration
    if (d != NULL) {
        *d = 3.14159;
        printf("Using sizeof(*d): Allocated %zu bytes for double\n", sizeof(*d));
        printf("Value: %f\n", *d);
        free(d);
    }
    
    printf("\nWhy sizeof(*pointer) is better:\n");
    printf("  - Type appears only once (in declaration)\n");
    printf("  - Changing type requires only one edit\n");
    printf("  - Less error-prone\n");
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 4: malloc() Returns Uninitialized Memory
 * =============================================================================
 * Shows that malloc() does NOT initialize memory to zero.
 */
void example4_uninitialized_memory(void) {
    printf("=== EXAMPLE 4: malloc() Returns Uninitialized Memory ===\n\n");
    
    printf("malloc() does NOT initialize memory!\n");
    printf("The memory contains whatever was there before (garbage).\n\n");
    
    // Allocate memory without initializing
    int *arr = (int*)malloc(10 * sizeof(int));
    
    if (arr == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    printf("Contents of uninitialized array (garbage values):\n");
    for (int i = 0; i < 10; i++) {
        printf("  arr[%d] = %d\n", i, arr[i]);
    }
    printf("\n");
    
    printf("This is why you should always initialize after malloc!\n\n");
    
    // Proper initialization
    printf("After initialization:\n");
    for (int i = 0; i < 10; i++) {
        arr[i] = 0;  // Or use memset
    }
    
    for (int i = 0; i < 10; i++) {
        printf("  arr[%d] = %d\n", i, arr[i]);
    }
    
    free(arr);
    printf("\n");
    
    // Alternative: use memset
    printf("Alternative: Using memset() for initialization:\n");
    int *arr2 = (int*)malloc(5 * sizeof(int));
    if (arr2 != NULL) {
        memset(arr2, 0, 5 * sizeof(int));  // Set all bytes to 0
        printf("After memset: ");
        for (int i = 0; i < 5; i++) {
            printf("%d ", arr2[i]);
        }
        printf("\n");
        free(arr2);
    }
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 5: calloc() - Zero-Initialized Memory
 * =============================================================================
 * Demonstrates calloc() which initializes memory to zero.
 */
void example5_calloc_usage(void) {
    printf("=== EXAMPLE 5: calloc() - Zero-Initialized Memory ===\n\n");
    
    printf("calloc(n, size) allocates n elements of 'size' bytes each,\n");
    printf("and initializes ALL bits to zero.\n\n");
    
    // -------------------------------------------------------------------------
    // Basic calloc usage
    // -------------------------------------------------------------------------
    printf("--- Basic calloc Usage ---\n");
    
    int *arr = (int*)calloc(10, sizeof(int));
    
    if (arr == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    printf("calloc(10, sizeof(int)) result:\n");
    for (int i = 0; i < 10; i++) {
        printf("  arr[%d] = %d\n", i, arr[i]);
    }
    printf("\nAll values are zero - no explicit initialization needed!\n\n");
    
    free(arr);
    
    // -------------------------------------------------------------------------
    // calloc vs malloc comparison
    // -------------------------------------------------------------------------
    printf("--- calloc vs malloc Comparison ---\n\n");
    
    printf("malloc(n * sizeof(int)):\n");
    printf("  - Faster (no initialization)\n");
    printf("  - Memory contains garbage\n");
    printf("  - Must initialize manually if zeros needed\n\n");
    
    printf("calloc(n, sizeof(int)):\n");
    printf("  - Slightly slower (zero initialization)\n");
    printf("  - Memory guaranteed to be zero\n");
    printf("  - Overflow protection: n * size computed safely\n\n");
    
    // -------------------------------------------------------------------------
    // Calloc for structures
    // -------------------------------------------------------------------------
    printf("--- calloc for Structures ---\n");
    
    typedef struct {
        int id;
        char name[50];
        float score;
    } Student;
    
    Student *students = (Student*)calloc(3, sizeof(Student));
    
    if (students != NULL) {
        printf("Allocated 3 Student structures with calloc:\n");
        for (int i = 0; i < 3; i++) {
            printf("  Student %d: id=%d, name=\"%s\", score=%.2f\n",
                   i, students[i].id, students[i].name, students[i].score);
        }
        printf("\nAll fields are zero/empty - safe to use immediately!\n");
        free(students);
    }
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 6: realloc() - Resizing Memory
 * =============================================================================
 * Demonstrates how to resize dynamically allocated memory.
 */
void example6_realloc_usage(void) {
    printf("=== EXAMPLE 6: realloc() - Resizing Memory ===\n\n");
    
    // -------------------------------------------------------------------------
    // Growing an array
    // -------------------------------------------------------------------------
    printf("--- Growing an Array ---\n");
    
    int capacity = 5;
    int *arr = (int*)malloc(capacity * sizeof(int));
    
    if (arr == NULL) {
        printf("Initial allocation failed!\n");
        return;
    }
    
    // Fill initial array
    for (int i = 0; i < capacity; i++) {
        arr[i] = i + 1;
    }
    
    printf("Initial array (capacity %d): ", capacity);
    for (int i = 0; i < capacity; i++) {
        printf("%d ", arr[i]);
    }
    printf("\nAddress: %p\n\n", (void*)arr);
    
    // Need more space - double the capacity
    int newCapacity = capacity * 2;
    int *temp = (int*)realloc(arr, newCapacity * sizeof(int));
    
    if (temp == NULL) {
        printf("Reallocation failed! Original array preserved.\n");
        free(arr);
        return;
    }
    
    arr = temp;  // Update pointer (might have moved)
    
    // Add more elements
    for (int i = capacity; i < newCapacity; i++) {
        arr[i] = i + 1;
    }
    
    printf("After realloc (capacity %d): ", newCapacity);
    for (int i = 0; i < newCapacity; i++) {
        printf("%d ", arr[i]);
    }
    printf("\nAddress: %p ", (void*)arr);
    printf("(may have changed)\n\n");
    
    capacity = newCapacity;
    
    // -------------------------------------------------------------------------
    // Shrinking an array
    // -------------------------------------------------------------------------
    printf("--- Shrinking an Array ---\n");
    
    newCapacity = 3;
    temp = (int*)realloc(arr, newCapacity * sizeof(int));
    
    if (temp != NULL) {
        arr = temp;
        printf("Shrunk to capacity %d: ", newCapacity);
        for (int i = 0; i < newCapacity; i++) {
            printf("%d ", arr[i]);
        }
        printf("\nData beyond new size is lost!\n\n");
    }
    
    free(arr);
    
    // -------------------------------------------------------------------------
    // realloc special cases
    // -------------------------------------------------------------------------
    printf("--- realloc Special Cases ---\n\n");
    
    printf("1. realloc(NULL, size) = malloc(size)\n");
    int *p1 = realloc(NULL, sizeof(int));  // Same as malloc
    if (p1 != NULL) {
        *p1 = 100;
        printf("   Created: %d\n", *p1);
        free(p1);
    }
    
    printf("\n2. realloc(ptr, 0) = free(ptr) (implementation-defined)\n");
    printf("   Note: Behavior varies; some return NULL, some non-NULL\n");
    printf("   Best practice: use free() explicitly instead\n");
    
    printf("\n3. If realloc fails, original memory is unchanged\n");
    printf("   Always use a temp pointer to check success!\n");
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 7: Proper Error Handling
 * =============================================================================
 * Demonstrates how to properly handle allocation failures.
 */
void example7_error_handling(void) {
    printf("=== EXAMPLE 7: Proper Error Handling ===\n\n");
    
    // -------------------------------------------------------------------------
    // Basic NULL check
    // -------------------------------------------------------------------------
    printf("--- Basic NULL Check ---\n");
    
    int *ptr = (int*)malloc(100 * sizeof(int));
    
    if (ptr == NULL) {
        fprintf(stderr, "Error: Failed to allocate memory\n");
        // Handle error: exit, return error code, etc.
    } else {
        printf("Allocation successful: %p\n", (void*)ptr);
        free(ptr);
    }
    printf("\n");
    
    // -------------------------------------------------------------------------
    // Safe wrapper function
    // -------------------------------------------------------------------------
    printf("--- Safe Wrapper Function ---\n");
    printf("Code pattern:\n\n");
    printf("void* safe_malloc(size_t size) {\n");
    printf("    void *ptr = malloc(size);\n");
    printf("    if (ptr == NULL) {\n");
    printf("        fprintf(stderr, \"Fatal: Out of memory\\n\");\n");
    printf("        exit(EXIT_FAILURE);\n");
    printf("    }\n");
    printf("    return ptr;\n");
    printf("}\n\n");
    
    // -------------------------------------------------------------------------
    // Handling realloc failure correctly
    // -------------------------------------------------------------------------
    printf("--- Correct realloc Pattern ---\n\n");
    
    printf("WRONG way:\n");
    printf("  ptr = realloc(ptr, newSize);  // Memory leak if fails!\n\n");
    
    printf("CORRECT way:\n");
    printf("  int *temp = realloc(ptr, newSize);\n");
    printf("  if (temp == NULL) {\n");
    printf("      // Handle error, ptr still valid\n");
    printf("      free(ptr);  // Or continue using ptr\n");
    printf("  } else {\n");
    printf("      ptr = temp;  // Update pointer\n");
    printf("  }\n\n");
    
    // Demonstrate
    int *arr = malloc(5 * sizeof(int));
    if (arr != NULL) {
        for (int i = 0; i < 5; i++) arr[i] = i;
        
        int *temp = realloc(arr, 10 * sizeof(int));
        if (temp == NULL) {
            printf("Realloc failed, but original 'arr' still valid\n");
            // Could continue using arr, or free it
        } else {
            arr = temp;
            printf("Realloc succeeded, pointer updated\n");
        }
        free(arr);
    }
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 8: Memory Leaks Demonstration
 * =============================================================================
 * Shows how memory leaks occur (educational - intentional leaks).
 */
void example8_memory_leaks(void) {
    printf("=== EXAMPLE 8: Memory Leaks Demonstration ===\n\n");
    
    printf("A memory leak occurs when allocated memory is never freed.\n");
    printf("Below are common causes (illustrated, but we clean up).\n\n");
    
    // -------------------------------------------------------------------------
    // Leak Type 1: Lost pointer
    // -------------------------------------------------------------------------
    printf("--- Leak Type 1: Lost Pointer ---\n");
    printf("Code:\n");
    printf("  void leaky() {\n");
    printf("      int *p = malloc(100);\n");
    printf("      // p goes out of scope without free()\n");
    printf("  }  // Memory leaked!\n\n");
    
    // -------------------------------------------------------------------------
    // Leak Type 2: Overwritten pointer
    // -------------------------------------------------------------------------
    printf("--- Leak Type 2: Overwritten Pointer ---\n");
    printf("Code:\n");
    printf("  int *p = malloc(100);\n");
    printf("  p = malloc(200);  // First allocation leaked!\n\n");
    
    // Correct version:
    int *p = malloc(100);
    if (p != NULL) {
        free(p);  // Free first!
        p = malloc(200);  // Then reallocate
        if (p != NULL) {
            free(p);
        }
    }
    printf("Correct: free() before reassigning\n\n");
    
    // -------------------------------------------------------------------------
    // Leak Type 3: Early return
    // -------------------------------------------------------------------------
    printf("--- Leak Type 3: Early Return ---\n");
    printf("Code:\n");
    printf("  int *p = malloc(100);\n");
    printf("  if (error) {\n");
    printf("      return;  // p leaked!\n");
    printf("  }\n");
    printf("  free(p);\n\n");
    
    printf("Correct: Always have cleanup path\n");
    printf("  int *p = malloc(100);\n");
    printf("  if (error) {\n");
    printf("      free(p);  // Clean up first!\n");
    printf("      return;\n");
    printf("  }\n");
    printf("  free(p);\n\n");
    
    // -------------------------------------------------------------------------
    // Detection tools
    // -------------------------------------------------------------------------
    printf("--- Memory Leak Detection Tools ---\n\n");
    printf("1. Valgrind (Linux): valgrind --leak-check=full ./program\n");
    printf("2. AddressSanitizer: gcc -fsanitize=address program.c\n");
    printf("3. Dr. Memory (Windows)\n");
    printf("4. LeakSanitizer: gcc -fsanitize=leak program.c\n");
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 9: Dangling Pointers
 * =============================================================================
 * Demonstrates the danger of using freed memory.
 */
void example9_dangling_pointers(void) {
    printf("=== EXAMPLE 9: Dangling Pointers ===\n\n");
    
    printf("A dangling pointer points to memory that has been freed.\n");
    printf("Using it causes UNDEFINED BEHAVIOR.\n\n");
    
    // -------------------------------------------------------------------------
    // Creating a dangling pointer
    // -------------------------------------------------------------------------
    printf("--- How Dangling Pointers Occur ---\n\n");
    
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 42;
    printf("Before free: ptr = %p, *ptr = %d\n", (void*)ptr, *ptr);
    
    free(ptr);
    // ptr is now dangling - it still holds the address, but memory is freed
    
    printf("After free:  ptr = %p (still has address!)\n", (void*)ptr);
    printf("             But memory at that address may be:\n");
    printf("             - Reused by another allocation\n");
    printf("             - Marked as inaccessible\n");
    printf("             - Contain garbage\n\n");
    
    // -------------------------------------------------------------------------
    // Prevention: Set to NULL
    // -------------------------------------------------------------------------
    printf("--- Prevention: Set to NULL ---\n\n");
    
    ptr = NULL;  // IMPORTANT: Prevents accidental use
    
    printf("After ptr = NULL:\n");
    printf("  ptr = %p\n", (void*)ptr);
    printf("  Accessing *ptr would cause segfault (detectable crash)\n");
    printf("  Can check: if (ptr != NULL) { ... }\n\n");
    
    // -------------------------------------------------------------------------
    // Double free danger
    // -------------------------------------------------------------------------
    printf("--- Double Free Danger ---\n\n");
    
    printf("WRONG:\n");
    printf("  free(ptr);\n");
    printf("  free(ptr);  // UNDEFINED BEHAVIOR!\n\n");
    
    printf("CORRECT:\n");
    printf("  free(ptr);\n");
    printf("  ptr = NULL;\n");
    printf("  free(ptr);  // Safe: free(NULL) does nothing\n\n");
    
    // Safe demonstration
    int *p2 = malloc(sizeof(int));
    free(p2);
    p2 = NULL;
    free(p2);  // This is safe
    printf("free(NULL) is always safe.\n");
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 10: Allocating Structures
 * =============================================================================
 * Shows how to dynamically allocate structures.
 */
void example10_allocating_structures(void) {
    printf("=== EXAMPLE 10: Allocating Structures ===\n\n");
    
    // -------------------------------------------------------------------------
    // Define a structure
    // -------------------------------------------------------------------------
    typedef struct {
        int id;
        char name[50];
        float gpa;
    } Student;
    
    // -------------------------------------------------------------------------
    // Single structure allocation
    // -------------------------------------------------------------------------
    printf("--- Single Structure Allocation ---\n");
    
    Student *s1 = (Student*)malloc(sizeof(Student));
    
    if (s1 == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    // Initialize
    s1->id = 101;
    strcpy(s1->name, "Alice Johnson");
    s1->gpa = 3.85;
    
    printf("Student: ID=%d, Name=%s, GPA=%.2f\n", 
           s1->id, s1->name, s1->gpa);
    printf("Size: %zu bytes\n", sizeof(Student));
    printf("Address: %p\n\n", (void*)s1);
    
    free(s1);
    
    // -------------------------------------------------------------------------
    // Array of structures
    // -------------------------------------------------------------------------
    printf("--- Array of Structures ---\n");
    
    int numStudents = 3;
    Student *students = (Student*)calloc(numStudents, sizeof(Student));
    
    if (students == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    // Initialize array
    students[0] = (Student){101, "Alice", 3.85};
    students[1] = (Student){102, "Bob", 3.50};
    students[2] = (Student){103, "Charlie", 3.92};
    
    printf("Student array:\n");
    for (int i = 0; i < numStudents; i++) {
        printf("  [%d] ID=%d, Name=%s, GPA=%.2f\n",
               i, students[i].id, students[i].name, students[i].gpa);
    }
    printf("Total size: %zu bytes\n\n", numStudents * sizeof(Student));
    
    free(students);
    
    // -------------------------------------------------------------------------
    // Structure with pointer members
    // -------------------------------------------------------------------------
    printf("--- Structure with Dynamic Members ---\n");
    
    typedef struct {
        int id;
        char *name;  // Dynamic string
        int *scores; // Dynamic array
        int numScores;
    } DynamicStudent;
    
    DynamicStudent *ds = malloc(sizeof(DynamicStudent));
    if (ds != NULL) {
        ds->id = 201;
        
        // Allocate name
        ds->name = malloc(50);
        if (ds->name != NULL) {
            strcpy(ds->name, "Dynamic Dave");
        }
        
        // Allocate scores array
        ds->numScores = 5;
        ds->scores = malloc(ds->numScores * sizeof(int));
        if (ds->scores != NULL) {
            int scores[] = {85, 90, 78, 92, 88};
            for (int i = 0; i < ds->numScores; i++) {
                ds->scores[i] = scores[i];
            }
        }
        
        printf("Dynamic Student:\n");
        printf("  ID: %d\n", ds->id);
        printf("  Name: %s\n", ds->name);
        printf("  Scores: ");
        for (int i = 0; i < ds->numScores; i++) {
            printf("%d ", ds->scores[i]);
        }
        printf("\n\n");
        
        printf("Remember: Free members BEFORE freeing structure!\n");
        
        // Free in reverse order
        free(ds->scores);
        free(ds->name);
        free(ds);
    }
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 11: 2D Array Allocation
 * =============================================================================
 * Demonstrates different ways to allocate 2D arrays dynamically.
 */
void example11_2d_array_allocation(void) {
    printf("=== EXAMPLE 11: 2D Array Allocation ===\n\n");
    
    int rows = 3, cols = 4;
    
    // -------------------------------------------------------------------------
    // Method 1: Array of pointers
    // -------------------------------------------------------------------------
    printf("--- Method 1: Array of Pointers ---\n");
    
    int **matrix1 = (int**)malloc(rows * sizeof(int*));
    if (matrix1 == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    for (int i = 0; i < rows; i++) {
        matrix1[i] = (int*)malloc(cols * sizeof(int));
        if (matrix1[i] == NULL) {
            // Cleanup previously allocated rows
            for (int j = 0; j < i; j++) {
                free(matrix1[j]);
            }
            free(matrix1);
            printf("Allocation failed!\n");
            return;
        }
    }
    
    // Initialize
    int val = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix1[i][j] = val++;
        }
    }
    
    printf("Matrix 1 (array of pointers):\n");
    for (int i = 0; i < rows; i++) {
        printf("  ");
        for (int j = 0; j < cols; j++) {
            printf("%3d ", matrix1[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    
    // Free
    for (int i = 0; i < rows; i++) {
        free(matrix1[i]);
    }
    free(matrix1);
    
    // -------------------------------------------------------------------------
    // Method 2: Single contiguous block
    // -------------------------------------------------------------------------
    printf("--- Method 2: Single Contiguous Block ---\n");
    
    int *matrix2 = (int*)malloc(rows * cols * sizeof(int));
    if (matrix2 == NULL) {
        printf("Allocation failed!\n");
        return;
    }
    
    // Access using index calculation: matrix2[i * cols + j]
    val = 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix2[i * cols + j] = val++;
        }
    }
    
    printf("Matrix 2 (contiguous block):\n");
    for (int i = 0; i < rows; i++) {
        printf("  ");
        for (int j = 0; j < cols; j++) {
            printf("%3d ", matrix2[i * cols + j]);
        }
        printf("\n");
    }
    printf("\n");
    
    // Single free
    free(matrix2);
    
    printf("Comparison:\n");
    printf("  Method 1: Flexible rows, multiple allocations\n");
    printf("  Method 2: Cache-friendly, single allocation\n");
    printf("\n");
}

/**
 * =============================================================================
 * EXAMPLE 12: Common Idioms and Patterns
 * =============================================================================
 * Shows common patterns for dynamic memory usage.
 */
void example12_common_patterns(void) {
    printf("=== EXAMPLE 12: Common Idioms and Patterns ===\n\n");
    
    // -------------------------------------------------------------------------
    // Pattern 1: Factory function
    // -------------------------------------------------------------------------
    printf("--- Pattern 1: Factory Function ---\n");
    printf("char* createString(const char *src) {\n");
    printf("    char *copy = malloc(strlen(src) + 1);\n");
    printf("    if (copy != NULL) {\n");
    printf("        strcpy(copy, src);\n");
    printf("    }\n");
    printf("    return copy;  // Caller must free\n");
    printf("}\n\n");
    
    // -------------------------------------------------------------------------
    // Pattern 2: Growable buffer
    // -------------------------------------------------------------------------
    printf("--- Pattern 2: Growable Buffer ---\n");
    
    int capacity = 4;
    int size = 0;
    int *buffer = malloc(capacity * sizeof(int));
    
    if (buffer != NULL) {
        // Add elements, growing when needed
        for (int i = 1; i <= 10; i++) {
            if (size >= capacity) {
                capacity *= 2;
                int *temp = realloc(buffer, capacity * sizeof(int));
                if (temp == NULL) {
                    printf("Resize failed!\n");
                    break;
                }
                buffer = temp;
                printf("Grew buffer to capacity %d\n", capacity);
            }
            buffer[size++] = i * 10;
        }
        
        printf("Buffer contents: ");
        for (int i = 0; i < size; i++) {
            printf("%d ", buffer[i]);
        }
        printf("\n\n");
        
        free(buffer);
    }
    
    // -------------------------------------------------------------------------
    // Pattern 3: Cleanup on error (goto cleanup)
    // -------------------------------------------------------------------------
    printf("--- Pattern 3: Cleanup on Error ---\n");
    printf("int process() {\n");
    printf("    int *a = malloc(100);\n");
    printf("    if (!a) goto cleanup;\n");
    printf("    int *b = malloc(200);\n");
    printf("    if (!b) goto cleanup;\n");
    printf("    // ... use a and b ...\n");
    printf("    free(b);\n");
    printf("    free(a);\n");
    printf("    return 0;\n");
    printf("cleanup:\n");
    printf("    free(b);  // free(NULL) is safe\n");
    printf("    free(a);\n");
    printf("    return -1;\n");
    printf("}\n\n");
    
    // -------------------------------------------------------------------------
    // Pattern 4: Output parameter
    // -------------------------------------------------------------------------
    printf("--- Pattern 4: Output Parameter ---\n");
    printf("int readData(int **data, int *size) {\n");
    printf("    *data = malloc(100 * sizeof(int));\n");
    printf("    if (*data == NULL) return -1;\n");
    printf("    *size = 100;\n");
    printf("    return 0;  // Success\n");
    printf("}\n");
    printf("// Caller: int *data; int size;\n");
    printf("//         readData(&data, &size);\n");
    printf("\n");
}

/**
 * =============================================================================
 * MAIN FUNCTION
 * =============================================================================
 */
int main(void) {
    printf("****************************************************************\n");
    printf("*    INTRODUCTION TO DYNAMIC MEMORY ALLOCATION - EXAMPLES      *\n");
    printf("****************************************************************\n\n");
    
    example1_static_vs_dynamic();
    example2_memory_layout();
    example3_malloc_basics();
    example4_uninitialized_memory();
    example5_calloc_usage();
    example6_realloc_usage();
    example7_error_handling();
    example8_memory_leaks();
    example9_dangling_pointers();
    example10_allocating_structures();
    example11_2d_array_allocation();
    example12_common_patterns();
    
    printf("****************************************************************\n");
    printf("*                    EXAMPLES COMPLETE                         *\n");
    printf("****************************************************************\n");
    
    return 0;
}
Examples - C Programming Tutorial | DeepML