Dynamic memory is where clean-looking C code can hide its worst bugs, and exam questions know it. A program may compile while leaking a block, reading uninitialised bytes or using a pointer after the object is gone.
The safe pattern is small: allocate the right number of bytes, check the result, resize through a temporary pointer, free exactly once and clear pointers you might otherwise reuse. Every classic dynamic-memory bug is one of those five steps done wrong, and the byte counts are where the mistake shows.
Stack vs heap and the four memory functions
An automatic local variable lives for the duration of its block. Its storage is reclaimed automatically when execution leaves that block, which is why implementations keep it on the stack.
Dynamically allocated storage comes from the heap. Its lifetime is not tied to the block that requested it. The allocation remains until the program passes its address to free, so ownership must be explicit.
The four functions to know are declared in <stdlib.h>:
Function | Purpose | Important result |
|---|---|---|
| Requests one block of | Bytes are uninitialised; returns |
| Requests space for | All bytes are initialised to zero; returns |
| Changes the size of an allocated block | May move the block; returns the new address or |
| Releases an allocated block | The old pointer value must not be dereferenced afterwards |
Allocation functions return void *, which converts to an object-pointer type in C. Do not hide a missing <stdlib.h> declaration by adding a cast. Include the header and check the returned pointer.
These byte counts assume sizeof(int) = 4 bytes, which is what mainstream 32-bit and 64-bit compilers give. The C standard guarantees only that an int holds at least 16 bits, so a size calculation always multiplies by sizeof rather than a hard-coded 4.
Worked example: a resizable int array
First request space for five integers:
int *a = malloc(5 * sizeof(int));
if (a == NULL) {
return;
}The byte calculation is:
Number of elements:
5Bytes per element:
4Requested size:
5 * 4 = 20 bytes
The 20 bytes are uninitialised, so reading a[0] before writing it would be undefined behaviour. Fill all five valid elements:
a[0] = 10;
a[1] = 20;
a[2] = 30;
a[3] = 40;
a[4] = 50;Now grow the array to eight integers. Use a temporary pointer:
int *tmp = realloc(a, 8 * sizeof(int));
if (tmp == NULL) {
free(a);
return;
}
a = tmp;The new request is 8 * 4 = 32 bytes. If it succeeds, realloc preserves the existing data up to the smaller of the old and new sizes. Therefore a[0] through a[4] still hold 10, 20, 30, 40, 50.
The extra 32 - 20 = 12 bytes provide space for three more integers because 12 / 4 = 3. Those new elements, a[5], a[6] and a[7], are uninitialised. Growing with realloc does not zero the added region.
For comparison, allocate five integers with calloc:
int *b = calloc(5, sizeof(int));
if (b == NULL) {
free(a);
return;
}This also requests 5 * 4 = 20 bytes, but all bytes start at zero, so b[0] through b[4] are zero in this integer example.
Release both allocations when they are no longer needed:
free(a);
a = NULL;
free(b);
b = NULL;Assigning NULL does not free memory. The preceding free does that. Clearing the pointer simply prevents this variable from continuing to hold a stale address.
Keeping a pointer's own value separate from the object it points to is what makes this kind of trace readable. Pointers in C for GATE builds that mental model with address diagrams.

The addresses in the figure are illustrative. The important rule is that a successful realloc may return the same address or a different one. Code must work in either case.
The realloc self-assignment trap
This compact line is unsafe:
a = realloc(a, 8 * sizeof(int));If the request succeeds, it appears to work. If it fails, realloc returns NULL and leaves the original allocation unchanged. The assignment then overwrites the only saved address with NULL. The original 20-byte block still exists, but the program can no longer reach or free it. The block leaks, and its five stored values are lost to the program.
The temporary-pointer form avoids that loss:
int *tmp = realloc(a, 8 * sizeof(int));
if (tmp == NULL) {
/* a still points to the original block */
free(a);
return;
}
a = tmp;The assignment to a occurs only after success. Until then, a retains ownership of the original block. In a program that can recover from the failed growth, you could keep using a instead of freeing it; the example frees it because the function is returning.
The dynamic-memory bug catalogue
Most exam questions draw from a short set of ownership failures.
Memory leak
A leak occurs when allocated storage remains live but the program has lost every pointer that could free it. Realloc self-assignment is one route. Another is assigning a fresh allocation to a without first freeing the block already owned by a.
Dangling pointer
After free(a), the variable a may still contain the old address, but no live object belongs to the program there. Dereferencing it, reading it or writing through it is undefined behaviour. Set a = NULL when that variable should no longer refer to anything.

Double free
Calling free(a) twice on the same block is undefined behaviour, because after the first call the block is no longer yours to release. Clearing the pointer after the first release helps because free(NULL) is safe, but ownership design is the real fix. Each block should have one clear release point.
Uninitialised read
malloc does not initialise its block. Reading an element before storing a value is undefined behaviour. calloc zero-initialises the allocated bytes, but that does not remove the need for a failure check.
Missing NULL check
Allocation can fail. Dereferencing the returned pointer before checking it can therefore dereference NULL. Test the result immediately, before filling the block.
Wrong size calculation
With sizeof(int) = 4, malloc(5) requests only 5 bytes. Five integers need 5 * sizeof(int) = 5 * 4 = 20 bytes. Prefer malloc(5 * sizeof *a) when a has the intended pointer type, because it stays correct if the pointed-to type changes.
Storage duration and allocated lifetime are related but different ideas. Storage Classes in C is the useful next comparison for understanding what the language manages automatically and what your code must release.
How exams test this and the short version
GATE Programming and Data Structures questions, CDAC C-CAT Section B, placement tests and viva rounds commonly ask which line leaks, whether a read is defined, what values survive a resize or which allocation is large enough.
For any snippet, write down four facts: allocated byte count, current owner pointer, initialised range and release point. In the worked example, those checks give 20 original bytes, 32 resized bytes, five preserved integers and three uninitialised new integers. Both calculations agree: the growth is 12 bytes, and 12 / 4 gives the three new slots.
KnowledgeGate's question bank carries over 600 questions on pointers alone, along with sets on heap allocation and memory-management bugs. Work through the C Language Course: Concepts, MCQs & Coding as the primary next step, with GATE Guidance by Sanchit Sir for PDS exam depth. The Coding & DSA Courses for Placements path connects these rules to programs you write yourself.
The short rule is: allocate the correct byte count, check NULL, resize through a temporary pointer, free each owned block once and null out a pointer that should no longer be used.




