malloc vs calloc in C: Which Is Faster? We Measured
malloc vs calloc measured on Linux: fresh vs reused memory, where zeroing really happens, why compilers turn malloc + memset into calloc, and overflow checks.
By Avinash KoriPublished 9 min read
On this page
malloc and calloc both allocate memory on the heap. The textbook difference is one sentence: calloc sets the memory to zero and malloc doesn't. The usual advice follows from it: "use malloc, because calloc wastes time zeroing".
We measured it, and the truth is in between. When memory comes fresh from the operating system, calloc is exactly as fast as malloc, because the zeroing is free. When your program reuses memory it freed earlier, calloc costs one memset of the block, at every size. It is never slower than writing malloc + memset yourself, and at -O2, GCC and Clang quietly turn that pair into calloc anyway.
All numbers below come from glibc 2.35 on Ubuntu 22.04 (the C library most Linux programs use), GCC 11.4 at -O2, on an Intel Core i5-7200U laptop.
The difference in one table
malloc(size) | calloc(count, size) | |
|---|---|---|
| Arguments | total bytes | number of elements and size of each |
| Contents | whatever was there before | all bytes zero |
Overflow of count * size | your problem | checked: returns NULL |
| Typical use | memory you fill right away | arrays, counters, structs that must start at zero |
| Free with | free() | free() |
Zero bytes mean 0 for integers, 0.0 for floating point on every common platform, and NULL for pointers on every platform you will meet in practice:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *counts = calloc(5, sizeof *counts);
if (counts == NULL) return 1;
counts[2] += 1;
for (int i = 0; i < 5; i++) printf("%d ", counts[i]);
printf("\n");
free(counts);
return 0;
}0 0 1 0 0With malloc, the same program would add 1 to whatever garbage happened to be in counts[2]. Reading memory before writing it is undefined behaviour, and the C compiler errors guide shows the -Wall warning that catches the simplest cases.
What "uninitialized" really means
Freshly allocated memory is not random. Very often it is the block your program freed a moment ago, with its old contents. Built with gcc -O0:
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char *first = malloc(64);
strcpy(first, "user=alice;password=hunter2");
uintptr_t old = (uintptr_t)first;
free(first);
char *second = malloc(64); /* usually the block that was just freed */
printf("same block: %s\n", (uintptr_t)second == old ? "yes" : "no");
printf("bytes 16 to 26: %.11s\n", second + 16);
free(second);
return 0;
}same block: yes
bytes 16 to 26: ord=hunter2glibc reused the block and overwrote only its first 16 bytes with its own bookkeeping; the rest of the old string was still there. A program that sends a malloc'd buffer over the network without filling all of it leaks whatever was there before. calloc makes that bug impossible. (At -O2 this demo prints nothing: the compiler deletes the strcpy, because nothing reads the string before free. For the same reason, clearing a password with memset just before free can disappear; use explicit_bzero or C23's memset_explicit.)
Benchmark: one large allocation
The first test allocates 512 MB in a fresh process, then touches one byte in every 4 KB page, the way a program that uses the whole buffer would. Times are the best of seven runs:
| Strategy | Allocation call | Touching every page | Total |
|---|---|---|---|
malloc | 0.02 ms | 170 ms | 170 ms |
calloc | 0.02 ms | 169 ms | 169 ms |
malloc + memset(p, 0, n) | 304 ms | 1 ms | 307 ms |
calloc took 0.02 ms for half a gigabyte. It didn't zero anything, and neither did malloc. For a request this large, glibc asks the kernel for fresh pages with mmap, and the kernel only promises that they will read as zero. Nothing is actually mapped until the program touches a page; the first touch triggers a page fault, and the kernel hands over a page it has just cleared. That is the 169 ms, and it is the same for both functions.
malloc + memset paid twice: memset caused all the page faults (with the kernel zeroing each page) and then wrote zeros over the already-zero pages. Same result, nearly double the time.
Benchmark: allocating and freeing in a loop
Most programs allocate a block, use it, free it and allocate again. The second test does exactly that, a few thousand to two million times per size, and reports the time per round trip (best of nine runs, pinned to one CPU core). First, the program touches one byte in each 4 KB page:
| Block size | malloc | calloc | malloc + memset |
|---|---|---|---|
| 64 bytes | 13 ns | 30 ns | 16 ns |
| 4 KB | 36 ns | 84 ns | 81 ns |
| 64 KB | 0.06 µs | 1.2 µs | 1.2 µs |
| 256 KB | 0.25 µs | 5.5 µs | 5.5 µs |
| 1 MB | 0.97 µs | 24 µs | 24 µs |
| 16 MB | 51 µs | 1,344 µs | 1,274 µs |
Here calloc is much slower than malloc, at every size, and costs the same as malloc + memset. The block is the one the program freed in the previous round, so it is full of old data, and calloc has to clear it. (The tiny blocks are a special case: in glibc 2.35, calloc doesn't use the per-thread cache that makes small malloc calls fast, so it lost even to malloc + memset at 64 bytes.)
The same loop, but now the program writes the whole block, as it would when filling a buffer with real data:
| Block size | malloc + fill | calloc + fill |
|---|---|---|
| 64 bytes | 17 ns | 33 ns |
| 4 KB | 81 ns | 135 ns |
| 64 KB | 1.2 µs | 2.6 µs |
| 1 MB | 23 µs | 46 µs |
| 16 MB | 1,160 µs | 2,623 µs |
When you overwrite everything anyway, calloc simply doubles the work: the block is written twice, once with zeros and once with your data.
Why 1 MB isn't "fresh" memory: the mmap threshold
The 512 MB test and the loop seem to disagree, and glibc's mmap threshold explains it. Blocks below the threshold come from the heap, which glibc reuses. Larger blocks are mapped straight from the kernel with mmap and returned with munmap, and those are always fresh, zero pages.
The threshold starts at 128 KiB, but it moves: when a program frees an mmapped block, glibc raises the threshold to that block's size (up to 32 MiB on 64-bit systems), so the next allocation of that size stays in the heap and is reused instead of being mapped and unmapped every time. That is why the 1 MB and 16 MB blocks in the loop were reused and cost calloc a full clear, while the 512 MB block, above 32 MiB, came fresh from the kernel.
The heap is still the faster choice. Fixing the threshold at 128 KiB (GLIBC_TUNABLES=glibc.malloc.mmap_threshold=131072) turns the dynamic behaviour off, so every 1 MB block is mapped fresh. calloc then costs nothing extra, but both functions take about 580 µs per round trip instead of 1–24 µs, because every page faults in again. Clearing reused memory is cheap compared with asking the kernel for new pages.
Your compiler already uses calloc
Here is the "manual" way to get zeroed memory:
#include <stdlib.h>
#include <string.h>
int *zeroed_array(size_t n) {
int *p = malloc(n * sizeof *p);
if (p) memset(p, 0, n * sizeof *p);
return p;
}Compile it with -O2 and look at the assembly (gcc -O2 -S). GCC 11 emits a single jmp calloc; Clang 14 emits call calloc. Both recognise malloc followed by a zeroing memset and replace the pair with calloc, so that large blocks get the free zeroing. At -O0 you get the malloc and memset calls you wrote. Our benchmark of -O optimization levels shows what else changes between those settings.
So writing malloc + memset "for speed" gives you, at best, the same code as calloc, and at -O0 (debug builds) slower code.
calloc checks for overflow; malloc can't
calloc(count, size) receives the two factors separately, so it can check that count * size fits in a size_t. With malloc you multiply yourself, and the multiplication can silently wrap:
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static void *volatile keep; /* stops the optimizer from deleting the allocations */
int main(void) {
size_t count = SIZE_MAX / 4 + 2; /* an element count read from a file, say */
size_t bytes = count * 4; /* wraps around to 4 */
int *a = malloc(bytes);
int *b = calloc(count, 4);
keep = a;
keep = b;
printf("count * 4 = %zu\n", bytes);
printf("malloc(count * 4): %s\n", a ? "succeeded (4 bytes!)" : "NULL");
printf("calloc(count, 4): %s\n", b ? "succeeded" : "NULL");
free(a);
free(b);
return 0;
}count * 4 = 4
malloc(count * 4): succeeded (4 bytes!)
calloc(count, 4): NULLThe keep lines matter if you try this yourself: without them, Clang at -O2 sees that the memory is never used, deletes the calloc call and assumes it succeeded, so the last line says succeeded. Optimizers may remove allocations the program can't observe.
The program believes it has room for four quintillion integers and has 4 bytes. Writing past them corrupts the heap, a classic exploitable bug when the count comes from untrusted input. GCC even warns about the calloc line here, because it can see the constant product; with a count read at run time, neither call gets a warning. If you need malloc or realloc semantics, check first (if (count > SIZE_MAX / size)), or use reallocarray(NULL, count, size), which glibc 2.26+ and the BSDs provide with the same check.
So which one should you use?
- Use
callocfor arrays and structs that must start at zero, and whenever the size is a count times an element size. It is never slower thanmalloc+memset(apart from the tiniest blocks), it is free for fresh memory from the kernel, and it checks for overflow. - Use
mallocwhen you are about to overwrite the whole block anyway: reading a file into a buffer, copying a string, filling an array in a loop. In a hot loop, clearing a block you immediately overwrite doubles the work. - Don't write
malloc+memset(0). It is whatcallocdoes, written longer. - Always check for
NULLandfreeexactly once. Our guide to pointers in C covers the rules for heap memory, and strings in C the buffer sizes that cause most overflows.
Common questions
Is calloc slower than malloc?
When memory is reused, yes: by exactly the time a memset of the block takes, which in our loop made calloc 2–26 times slower than a bare malloc. When the memory comes fresh from the kernel (a first large allocation, or any block above 32 MiB with glibc's defaults), calloc costs the same as malloc, because new pages are already zero. Compared with malloc + memset, calloc is never meaningfully slower.
Does malloc return zeroed memory?
Sometimes, by accident: fresh pages from the operating system are zero. Memory that your program freed and allocated again keeps its old contents. Never rely on it; use calloc when you need zeros.
Can I use calloc for a single struct?
Yes: struct config *c = calloc(1, sizeof *c); gives a struct with every integer field 0, every pointer NULL and every double 0.0.
Is calloc the same as malloc plus memset?
The result is the same, and at -O2 GCC and Clang compile malloc + memset(0) into a calloc call. calloc is often faster than the pair you wrote, because it can skip zeroing memory that is already zero, and it checks count * size for overflow.
Does C++ have an equivalent?
new int[n]() and std::vector<int>(n) value-initialise to zero. std::vector is the usual choice in C++; try it in the C++ Compiler.
Try it yourself
Paste any example into the online C Compiler, which builds with Clang at -O2 and runs the program in your browser. Its C library and allocator are not glibc, so the reuse demo can print different bytes there, which is a good reminder that uninitialized memory has no defined contents. The overflow example behaves the same: size_t is 32 bits there, and the product still wraps to 4.

Written by
Avinash Kori
Founder of Devtoolry
Avinash is a full-stack web developer from Surat, India, with eight years of experience in PHP, Laravel and Angular. He built Devtoolry so developers can solve everyday problems quickly with free tools that run entirely in the browser, and writes practical guides on the formats, encodings and techniques behind them.
Try it yourself
The free tools used in this article. They run in your browser, so nothing you paste is uploaded.
C Compiler
Compile and run C code online with Clang 21, a GCC-compatible compiler: pick C89 (ANSI C), C99, C11, C17 or C23 and add stdin input. Your code stays private.
C++ Compiler
Run C++ code online for free: compile C++20 with Clang 21 and libc++, type stdin input and see the output. It runs in your browser, so your code stays private.