Tutorials Logic, IN info@tutorialslogic.com

C Dynamic Memory malloc, calloc, realloc,

Heap Ownership

Dynamic allocation gives a C program storage whose size or lifetime is decided at runtime. malloc and calloc create a block, realloc requests a resized block, and free ends the allocation’s lifetime. The key design question is ownership: exactly one part of the program must know who releases each successful allocation.

After this lesson, you can calculate allocation sizes defensively, handle allocation failure without losing the original pointer, initialize new storage, avoid use-after-free and double-free defects, and design a cleanup path that works after partial failure.

Automatic and Dynamic Storage

Automatic local objects normally live until execution leaves their block. Dynamically allocated objects live from a successful allocation until free, which allows their size and lifetime to outlast one function call. The C standard does not require a particular stack or heap size; limits come from the implementation and execution environment.

Feature Stack Heap
Allocation Automatic (on function call) Manual (malloc/calloc/realloc)
Deallocation Automatic (on function return) Manual (free)
Limit Implementation and environment dependent Available address space and allocator limits
Speed Fast Slower (OS involvement)
Use case Local variables, function calls Large/variable-size data, long-lived data

Dynamic Memory Functions

All dynamic memory functions are in <stdlib.h>:

Function Description Initialization
malloc(size) Allocates size bytes Uninitialized (garbage values)
calloc(n, size) Allocates n x size bytes Zero-initialized
realloc(ptr, size) Resizes previously allocated block Preserves existing data
free(ptr) Releases allocated memory -

malloc and free - Dynamic Array

malloc and free - Dynamic Array
#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    printf("Enter number of elements: ");
    scanf("%d", &n);

    // malloc - allocate n integers (uninitialized)
    int *arr = malloc((size_t)n * sizeof *arr);
    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // Fill array
    for (int i = 0; i < n; i++) {
        arr[i] = (i + 1) * 10;
    }

    // Print array
    printf("Array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // ALWAYS free when done
    free(arr);
    arr = NULL;  // good practice: avoid dangling pointer

    printf("Memory freed.\n");
    return 0;
}

/*
Enter number of elements: 5
Array: 10 20 30 40 50
Memory freed.
*/

calloc and realloc

calloc and realloc
#include <stdio.h>
#include <stdlib.h>

int main() {
    // calloc - allocates and zero-initializes
    int *arr = (int*)calloc(5, sizeof(int));
    if (!arr) { printf("calloc failed\n"); return 1; }

    printf("calloc (all zeros): ");
    for (int i = 0; i < 5; i++) printf("%d ", arr[i]);  // 0 0 0 0 0
    printf("\n");

    // Fill with values
    for (int i = 0; i < 5; i++) arr[i] = i + 1;

    // realloc - resize to 10 elements
    int *bigger = realloc(arr, 10 * sizeof *arr);
    if (!bigger) {
        printf("realloc failed\n");
        free(arr);
        return 1;
    }
    arr = bigger;  // arr now points to the resized block

    // Initialize new elements
    for (int i = 5; i < 10; i++) arr[i] = (i + 1) * 10;

    printf("After realloc (10 elements): ");
    for (int i = 0; i < 10; i++) printf("%d ", arr[i]);
    printf("\n");

    free(arr);
    return 0;
}

/*
calloc (all zeros): 0 0 0 0 0
After realloc (10 elements): 1 2 3 4 5 60 70 80 90 100
*/

Dynamic Array of Structs

Dynamic Array of Structs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char name[50];
    int  score;
} Student;

int main() {
    int n;
    printf("How many students? ");
    scanf("%d", &n);

    // Allocate array of structs dynamically
    Student *students = malloc((size_t)n * sizeof *students);
    if (!students) { printf("Allocation failed\n"); return 1; }

    // Input data
    for (int i = 0; i < n; i++) {
        printf("Enter name and score for student %d: ", i + 1);
        scanf("%s %d", students[i].name, &students[i].score);
    }

    // Find highest scorer
    int maxIdx = 0;
    for (int i = 1; i < n; i++) {
        if (students[i].score > students[maxIdx].score) maxIdx = i;
    }

    printf("\nAll students:\n");
    for (int i = 0; i < n; i++) {
        printf("  %-15s %d\n", students[i].name, students[i].score);
    }
    printf("Top scorer: %s (%d)\n", students[maxIdx].name, students[maxIdx].score);

    free(students);
    students = NULL;
    return 0;
}

Allocation Size

Allocate by the pointed type, for example malloc(count * sizeof *items). In C, do not cast the void pointer returned by malloc; a cast can hide a missing <stdlib.h> declaration. Before multiplying count by element size, ensure the multiplication cannot overflow SIZE_MAX. A wrapped size can allocate a smaller block than the later loop expects.

malloc leaves the bytes indeterminate; initialize every element before reading it. calloc performs the size multiplication and initializes all bits to zero, which produces zero for integer types but should not be described as constructing every possible semantic default. A zero-size allocation has implementation-defined usability details, so handle an empty collection explicitly.

Safe Reallocation

realloc may extend the existing block or allocate a new block, copy preserved bytes, and free the old block. If it fails for a nonzero requested size, it returns NULL and the original allocation remains valid. Assign the result to a temporary pointer; assigning directly to the only pointer leaks the original block on failure.

New bytes added by a successful growth are uninitialized. Initialize them before use. Any pointer into the old block becomes invalid if realloc moves the allocation, so store indexes or offsets rather than long-lived interior pointers across a resize.

Cleanup Paths

free accepts NULL, which makes a single cleanup section practical after partial initialization. After free, all pointers to the allocation become dangling; setting one variable to NULL does not repair aliases held elsewhere. The durable solution is a clear ownership rule that prevents those aliases from being used.

Use runtime tools when available: compiler sanitizers can detect many out-of-bounds and use-after-free defects, while leak detectors reveal allocations that lost their owner. Tools support the ownership model; they do not replace one.

Before you move on

Ownership Review

4 checks
  • Check count-by-size multiplication before allocating.
  • Test every allocation result before dereferencing it.
  • Use a temporary pointer for realloc.
  • Release each owned block once on every exit path.

Heap Defects

  • Assigning realloc directly to the original pointer.

    Store the result temporarily and replace the owner only after success.
  • Reading newly allocated or grown bytes.

    Initialize the required range before reading it.
  • Assuming ptr = NULL fixes all aliases after free.

    Design ownership so no other code uses the ended allocation.

Try this next

Grow an Array Safely

0 of 2 completed

Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.