Dynamic memory allocation in C: interview questions, with answers
C gives you memory by hand and expects it back by hand, and interview questions on dynamic allocation test whether you can be trusted with that. They ask where each kind of variable lives, what the four allocation functions promise, what happens when a block is freed twice or used after it is freed, and how to find a leak before it takes a server down. Each answer below comes with code that was compiled and run, and every block it allocates is also freed.
The questions start with the stack and the heap, move through the allocation functions and the ways they go wrong, and end with two allocations interviewers like to see written out. Then take the free C diagnostic — ten questions across every C topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What is the difference between stack and heap memory in C?
In short: Local variables live on the stack and vanish when their function returns; heap blocks come from malloc and live until free, at a cost in speed and bookkeeping.
Automatic variables, the locals and parameters of a function, live in its stack frame. Allocating them is just a move of the stack pointer, so it is very fast, but the memory is released the moment the function returns, and the stack is small, a few megabytes. The heap holds memory requested with malloc, calloc or realloc: it can be large, its size can be decided at run time, and it lives until you call free, so it can outlive the function that allocated it, as the int below does. The price is a slower allocator call, fragmentation, and the duty to free every block exactly once. Static and global variables are a third region, created before main starts and kept for the whole run.
int *heap_int(int v) { int *p = malloc(sizeof *p); if (p) *p = v; return p; } int main(void) { int *p = heap_int(21); printf("%d\n", *p); free(p); } // 21
2.What do malloc, calloc, realloc and free each do?
In short: malloc allocates uninitialised bytes, calloc allocates zeroed memory for n elements, realloc resizes a block, possibly moving it, and free returns a block to the allocator.
malloc(size) returns a block of at least size bytes with indeterminate contents. calloc(n, size) allocates n elements of size bytes each and sets every byte to zero, which is why a[0] below reads 0. realloc(p, size) resizes the block p points to: it may extend the block in place or allocate a new one, copy the old contents across and free the old block, so after it succeeds only the pointer it returned is valid, and realloc(NULL, size) behaves like malloc. free(p) releases a block from any of the three, and free(NULL) does nothing. Every successful allocation must be freed exactly once, with free, never with C++'s delete, and memory from new must never be passed to free.
int *a = calloc(3, sizeof *a); a[1] = 5; int *b = realloc(a, 4 * sizeof *b); if (b) { a = b; a[3] = 8; } printf("%d %d %d\n", a[0], a[1], a[3]); // 0 5 8 free(a);
3.What is a memory leak in C, and how do you find one?
In short: A leak is heap memory that is still allocated but no longer reachable, so it can never be freed; Valgrind or AddressSanitizer's leak checker report where each lost block was allocated.
A block leaks when the last pointer to it is lost while it is still allocated: the pointer goes out of scope, is overwritten, or an early return skips the free. The memory comes back only when the process exits, so short programs survive leaks, but a server that leaks on every request grows until it runs out. Finding them is tool work. Valgrind's memcheck runs the program on a synthetic CPU and lists every unfreed block with the call stack that allocated it, and the -fsanitize=address option of gcc and clang includes LeakSanitizer, which reports leaks when the program exits. Prevention is structural: give every allocation a clear owner, and free on every exit path, as the function below does after using its copy.
int is_tag(const char *s) { char *copy = malloc( strlen(s) + 1); if (!copy) return -1; strcpy(copy, s); int ok = copy[0] == '#'; free(copy); return ok; } int main(void) { int r = is_tag("#1"); printf("%d\n", r); } // 1
4.What happens if you free a pointer twice, or use memory after freeing it?
In short: Both are undefined behaviour: a double free can corrupt the allocator's bookkeeping, and a use after free reads or writes memory that may already belong to something else.
free hands the block back to the allocator, which may reuse it for the next malloc or keep its own bookkeeping inside it. Freeing it a second time corrupts that bookkeeping; modern allocators detect the simplest cases and abort with a message such as 'double free detected', but only sometimes. Using the block after free is worse, because nothing stops it from appearing to work until the memory is reused, when it silently corrupts another object, and attackers exploit exactly that. Setting the pointer to NULL straight after free makes a second free harmless, since free(NULL) does nothing, as the code shows, and makes a later use crash at once instead of corrupting data. AddressSanitizer reports both bugs at the faulting line.
char *buf = malloc(8); strcpy(buf, "ok"); printf("%s\n", buf); // ok free(buf); buf = NULL; free(buf); puts("free(NULL) is a no-op"); // free(NULL) is a no-op
5.Should you cast the result of malloc in C?
In short: No: void * converts to any object pointer implicitly in C, so the cast adds nothing and once hid a missing #include <stdlib.h>; in C++ the cast is required.
malloc returns void *, and C converts void * to any object pointer type automatically, so int *p = malloc(n * sizeof *p); is complete and correct. The cast was harmful in C89: if you forgot #include <stdlib.h>, the compiler assumed malloc returned int, and the cast silenced the warning while truncating 64-bit addresses. Modern compilers reject calls to undeclared functions, so that danger has mostly gone, but the cast still repeats the type and can drift out of date. Writing sizeof *p rather than sizeof(double) has the same benefit: the size follows the pointer's type if it changes. C++ does not convert void * implicitly, so C code compiled as C++ needs the cast, though C++ code should use new or containers instead.
size_t n = 3; double *d = malloc( n * sizeof *d); if (d) { d[2] = 1.5; printf("%.1f\n", d[2]); free(d); } // 1.5
6.How do you allocate a two-dimensional array dynamically in C?
In short: Either allocate one contiguous block and index it through a pointer to an array of n columns, or allocate an array of row pointers and then each row separately.
The contiguous approach, int (*m)[n] = malloc(rows * sizeof *m);, allocates one block, keeps rows adjacent for the cache, is released with a single free, and still lets you write m[i][j], because m is a pointer to an array of n ints; since C99 n may be a run-time value, as below. The array-of-pointers approach allocates int **m with one pointer per row and then each row with its own malloc, which allows rows of different lengths but needs rows + 1 allocations, rows + 1 frees in the right order, and a check after every allocation. Interviewers mainly want to hear that trade-off, and to see every allocation freed.
int rows = 2, n = 3; int (*m)[n] = malloc(rows * sizeof *m); for (int i = 0; i < rows; i++) for (int j = 0; j < n; j++) m[i][j] = i * 7 + j; printf("%d\n", m[1][2]); // 9 free(m);
7.How do you make a heap copy of a string in C, and why is it strlen + 1?
In short: Allocate strlen(s) + 1 bytes, the characters plus the terminator, and copy them with memcpy or strcpy; POSIX and C23 provide strdup, which does both steps.
strlen counts the characters before the terminator, so a buffer of strlen(s) bytes has no room for the '\0' every string needs, and copying into it writes one byte past the end, the most common off-by-one in C. The correct allocation is strlen(s) + 1, and memcpy with that same length copies the terminator too, as below. strdup does both steps and returns a block the caller must free; it was a POSIX function for decades and joined the C standard in C23, together with strndup. When the length is already known, keep it in a variable instead of calling strlen again, since every call scans the whole string.
char *clone(const char *s) { size_t n = strlen(s) + 1; char *p = malloc(n); if (p) memcpy(p, s, n); return p; } int main(void) { char *c = clone("heap"); printf("%s %zu\n", c, strlen(c)); free(c); } // heap 4
How the diagnostic asks it
One question from the C bank, exactly as a sitting would show it. The bank has 4 on dynamic memory and 30 across C.
What is wrong with this C code?
char *s = malloc(10); s = malloc(20); free(s);
- 1Nothing: free(s) releases both blocks
- 2The first 10-byte block leaks: its only pointer is overwritten before it is freedcorrect
- 3It frees the same block twice
- 4It does not compile: s is assigned twice
Each call to malloc returns a separate block. The second assignment overwrites s, the only variable holding the first block's address, so that block can never be freed: a memory leak of 10 bytes. free(s) releases only the block s points to now, the 20-byte one; free knows nothing about any other block. Nothing is freed twice, so it is not a double free. It compiles: a pointer variable can be reassigned any number of times. A leak like this inside a loop grows until memory runs out; tools such as Valgrind report it.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 C questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.