Skip to content
Laviyx Learn

Try

C course menu

Dynamic Memory Allocation in C: malloc, calloc, realloc and free

Learn dynamic memory in C: stack versus heap, allocating with malloc and calloc, checking for NULL, resizing safely with realloc, releasing memory with free, building a growable array, and allocating strings and 2D arrays.

Intermediate10 min readUpdated
On this page

So far every variable's size was fixed when you wrote the program. Dynamic memory lets a program request memory at run time, when it knows how much it needs, and keep it for as long as it wants.

Stack and heap#

StackHeap
Used forLocal variables and function callsMemory you request with malloc and friends
LifetimeEnds automatically when the block returnsUntil you call free
SizeSmall (typically a few MB)Large (limited by available memory)
Managed byThe compilerYou

malloc and free#

malloc(bytes) (from <stdlib.h>) returns a pointer to a new block of uninitialised memory, or NULL if it fails. free(ptr) gives the block back:

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

int main(void) {
    int n = 5;
    int *squares = malloc(n * sizeof *squares);
    if (squares == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }

    for (int i = 0; i < n; i++) squares[i] = (i + 1) * (i + 1);
    for (int i = 0; i < n; i++) printf("%d ", squares[i]);
    printf("\n");

    free(squares);
    squares = NULL;     /* avoid using it again by accident */
    return 0;
}
Output
1 4 9 16 25 
  • n * sizeof *squares keeps the size correct even if the element type changes later.
  • Always check for NULL.
  • Every successful malloc needs exactly one free. free(NULL) is allowed and does nothing.

calloc#

calloc(count, size) allocates an array and sets every byte to zero:

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

int main(void) {
    int *votes = calloc(4, sizeof *votes);
    if (votes == NULL) return 1;

    int ballots[] = {2, 0, 2, 3, 2, 1, 0};
    for (int i = 0; i < 7; i++) votes[ballots[i]]++;
    for (int i = 0; i < 4; i++) printf("candidate %d: %d\n", i, votes[i]);

    free(votes);
    return 0;
}
Output
candidate 0: 2
candidate 1: 1
candidate 2: 3
candidate 3: 1

realloc: growing a block#

realloc(ptr, new_size) resizes a block, moving it if necessary and keeping the old contents. If it fails it returns NULL and leaves the old block untouched, so assign the result to a temporary first:

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

int main(void) {
    int capacity = 2, count = 0;
    int *items = malloc(capacity * sizeof *items);
    if (items == NULL) return 1;

    for (int value = 10; value <= 70; value += 10) {
        if (count == capacity) {
            int new_capacity = capacity * 2;
            int *bigger = realloc(items, new_capacity * sizeof *items);
            if (bigger == NULL) {
                free(items);
                return 1;
            }
            items = bigger;
            capacity = new_capacity;
            printf("grew to %d\n", capacity);
        }
        items[count++] = value;
    }

    for (int i = 0; i < count; i++) printf("%d ", items[i]);
    printf("\n");
    free(items);
    return 0;
}
Output
grew to 4
grew to 8
10 20 30 40 50 60 70 

items = realloc(items, ...) would lose the only pointer to the old block if realloc failed, leaking it. Doubling the capacity keeps the number of reallocations small; this is how growable arrays such as C++'s std::vector work.

Allocating a string#

strlen does not count the '\0', so allocate one extra byte:

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

char *join(const char *a, const char *b) {
    size_t len = strlen(a) + strlen(b);
    char *out = malloc(len + 1);         /* + 1 for '\0' */
    if (out == NULL) return NULL;
    strcpy(out, a);
    strcat(out, b);
    return out;                          /* the caller must free it */
}

int main(void) {
    char *s = join("dynamic ", "memory");
    if (s == NULL) return 1;
    printf("%s (%zu)\n", s, strlen(s));
    free(s);
    return 0;
}
Output
dynamic memory (14)

A function that returns allocated memory should say clearly, in its documentation, that the caller owns it and must free it.

A 2D array on the heap#

Allocate one block and compute the index yourself; it needs one malloc and one free:

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

int main(void) {
    int rows = 3, cols = 4;
    int *grid = malloc((size_t)rows * cols * sizeof *grid);
    if (grid == NULL) return 1;

    for (int r = 0; r < rows; r++)
        for (int c = 0; c < cols; c++)
            grid[r * cols + c] = r * 10 + c;

    printf("%d %d\n", grid[1 * cols + 2], grid[2 * cols + 3]);
    free(grid);
    return 0;
}
Output
12 23

Practice#

Practice exercise

Write int *range(int start, int end, int *out_len) that allocates and returns an array containing start, start + 1, ..., end - 1, storing the length through out_len. Print range(3, 9, &len) and free it.

Show solution
C
#include <stdio.h>
#include <stdlib.h>

int *range(int start, int end, int *out_len) {
    int n = end > start ? end - start : 0;
    *out_len = n;
    if (n == 0) return NULL;
    int *a = malloc(n * sizeof *a);
    if (a == NULL) {
        *out_len = 0;
        return NULL;
    }
    for (int i = 0; i < n; i++) a[i] = start + i;
    return a;
}

int main(void) {
    int len;
    int *r = range(3, 9, &len);
    for (int i = 0; i < len; i++) printf("%d ", r[i]);
    printf("(length %d)\n", len);
    free(r);
    return 0;
}
Output
3 4 5 6 7 8 (length 6)

Key takeaway#

Finished this lesson?