Educational Blog

How to Use malloc in C

Learn malloc basics, safe allocation patterns, and common mistakes in C.

malloc is the entry point for dynamic memory in C. It gives you a block of bytes on the heap, returns a pointer to the first byte, and leaves initialization up to you. That last part matters more than beginners expect: malloc allocates memory, but it does not clear it, and it does not know what type of object you intend to store there.

If you are coming from stack variables or arrays, malloc can feel abstract at first. The shortest useful mental model is this: the stack is for short-lived, automatically managed storage, while the heap is for memory you request manually and release manually. That tradeoff is powerful, but it also means you are responsible for avoiding leaks, dangling pointers, and buffer overruns.

What malloc actually does

malloc stands for memory allocation. In C, you usually include it through #include <stdlib.h>. Its signature is roughly:

void *malloc(size_t size);

It accepts the number of bytes you want and returns a void * pointing to a block of memory large enough to hold that many bytes. If allocation fails, it returns NULL.

A few practical facts matter immediately:

  • The returned pointer is untyped until you cast or assign it.
  • The bytes are uninitialized.
  • The block must later be released with free.
  • The allocated region is contiguous.

That last point makes malloc especially useful for arrays, structs, and binary buffers.

A first example

Here is the simplest form of malloc usage for an integer:

#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof(int));
    if (p == NULL) {
        printf("Allocation failed\n");
        return 1;
    }

    *p = 42;
    printf("%d\n", *p);

    free(p);
    return 0;
}

This example does three things correctly:

  1. It asks for enough bytes for one int.
  2. It checks whether the allocation worked.
  3. It frees the memory when done.

Notice that malloc(sizeof(int)) does not create an int in the same sense that int x; does. It creates raw storage that can hold an int once you put one there.

Why sizeof matters

You should almost never hardcode allocation sizes for typed objects. Use sizeof so the code stays correct across platforms and remains readable.

For a single struct:

MyType *item = malloc(sizeof(MyType));

For an array:

int *numbers = malloc(count * sizeof(int));

A common best practice is to write:

int *numbers = malloc(count * sizeof *numbers);

That version is safer because it derives the element size from the pointer variable itself. If the type changes later, the allocation expression usually changes with it.

When to use malloc

malloc is the right tool when:

  • the size is not known at compile time
  • the object must outlive the current function
  • you need a large buffer that should not live on the stack
  • you want to build dynamic data structures such as linked lists, trees, or resizable arrays

It is usually not the right tool when:

  • the object is tiny and only needed inside one function
  • a fixed-size local array is simpler
  • you can use a higher-level container in another language or library

The rule is not “use malloc whenever possible.” The rule is “use it when manual lifetime control is actually needed.”

malloc versus calloc and realloc

These three functions are related, but they solve different problems.

FunctionWhat it doesCommon use
mallocAllocates raw uninitialized memoryNew objects, buffers, arrays
callocAllocates and zero-initializes memoryArrays that should start at 0
reallocResizes an existing allocationGrowing or shrinking dynamic buffers

calloc is often helpful when you want known initial values. realloc is useful for dynamic growth, but you need to handle failure carefully so you do not lose the original pointer.

Example of calloc:

int *arr = calloc(10, sizeof(int));

This gives you 10 integers, all initialized to zero.

Common mistakes

The hardest part of malloc is not calling it. It is managing the memory safely afterward.

1. Forgetting to free

If you allocate memory and never release it, the program leaks memory.

char *buffer = malloc(1024);
if (buffer == NULL) return 1;
/* use buffer */
free(buffer);

Every successful allocation should have a clear ownership path that ends in free.

2. Using the pointer after free

Once memory is freed, the pointer becomes invalid. Accessing it is undefined behavior.

free(p);
*p = 10; /* wrong */

A good habit is to set it to NULL after freeing if the variable remains in scope.

3. Allocating the wrong size

This is subtle and dangerous.

int *arr = malloc(10); /* wrong: 10 bytes, not 10 ints */

If int is 4 bytes, you only allocated room for 2 or 3 integers, depending on alignment and platform assumptions. Always multiply by the element size.

4. Forgetting to check for NULL

Allocation can fail.

char *data = malloc(n);
if (data == NULL) {
    /* handle failure */
}

Ignoring failure makes later code brittle and can crash the program.

5. Losing the original pointer on realloc

This is a classic bug:

p = realloc(p, new_size);

If realloc fails, it returns NULL and the original pointer is still valid, but you may have overwritten it and lost access to the block. Use a temporary variable instead.

void *tmp = realloc(p, new_size);
if (tmp == NULL) {
    /* p is still valid here */
} else {
    p = tmp;
}

A safer pattern for arrays

When working with dynamic arrays, it helps to follow a repeatable pattern:

size_t count = 100;
int *values = malloc(count * sizeof *values);
if (values == NULL) {
    return 1;
}

for (size_t i = 0; i < count; i++) {
    values[i] = (int)i;
}

free(values);

This pattern keeps the code readable and reduces mistakes:

  • the count is a size_t
  • the allocation size matches the element type
  • the pointer is checked before use
  • the memory is freed once it is no longer needed

malloc for structs

One of the most common uses of malloc is allocating a struct dynamically.

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

typedef struct {
    char name[32];
    int age;
} Person;

Person *p = malloc(sizeof *p);
if (p == NULL) {
    return 1;
}

strncpy(p->name, "Ava", sizeof p->name - 1);
p->name[sizeof p->name - 1] = '\0';
p->age = 28;

free(p);

This is useful when:

  • the struct is large
  • the struct should be shared across functions
  • the object needs to survive after the current function returns

If the object does not need that lifetime, a local variable is usually simpler:

Person p;

Ownership discipline

The most important habit with malloc is to decide who owns the memory.

A few clean rules help:

  • The function that allocates should document who frees.
  • If a function receives a pointer, decide whether it borrows or owns it.
  • Use free exactly once for each successful allocation.
  • Do not share the same pointer casually across unrelated parts of the code unless the ownership model is explicit.

This is why many C codebases establish conventions early. Without them, the code becomes hard to reason about.

Practical checklist

Before you use malloc, run through this checklist:

  1. Did you include <stdlib.h>?
  2. Are you allocating the right number of bytes?
  3. Did you check the return value against NULL?
  4. Have you initialized the memory before reading it?
  5. Do you know exactly where free will happen?
  6. Are you avoiding use-after-free and double-free bugs?

If the answer to any of these is unclear, pause and fix the ownership model before adding more code.

Common comparison with stack allocation

Here is the practical difference:

  • Stack allocation is automatic and fast.
  • Heap allocation is manual and flexible.

Use stack allocation when the lifetime is local and the size is fixed. Use malloc when the lifetime or size is dynamic.

Example:

void f(void) {
    int local = 5;        /* stack */
    int *heap = malloc(sizeof *heap); /* heap */

    if (heap != NULL) {
        *heap = local;
        free(heap);
    }
}

The stack variable disappears automatically when f returns. The heap allocation stays alive until free is called.

Bottom line

malloc is simple at the syntax level and demanding at the design level. If you remember only three things, make them these:

  • allocate the correct number of bytes
  • check for failure
  • free what you allocate

Once those habits are automatic, malloc becomes a reliable tool for arrays, structs, buffers, and any C program that needs dynamic memory.

For a deeper next step, study calloc, realloc, and common ownership patterns in real C code. That is where dynamic memory moves from basic usage into maintainable programming practice.

Written by

c-double.com Editorial Team

Editorial team

c-double.com publishes practical how-to guides and educational articles with clear steps and useful context.