A fixed array works when its element count is known while writing the program, but real input can grow at runtime. Four similar library calls have different rules. The runnable program allocates four marks, grows the block to six, calculates a checked result and releases it cleanly. At each step, ask: how many bytes were requested, which pointer owns the live block, and which values are initialized?
Dynamic memory allocation in C: what changes at runtime
int fixed[4] = {72, 85, 91, 68}; has a fixed size. With int *marks = malloc(count * sizeof *marks);, count can arrive at runtime. The pointer and allocated object are separate: the block remains allocated until free() releases it or successful realloc() replaces it.
The pointer is typically in main's stack frame and the block in the heap, but portable C follows lifetime and ownership, not physical placement. Under sizeof(int) == 4, four integers need 4 x 4 = 16 bytes. Allocate dynamically when size arrives at runtime, an object must outlive its creating function, or a structure must grow. Coding and Skill Development Courses covers the broader path.
malloc, calloc, realloc and free have distinct jobs
Call | Purpose | Initial contents | Failure or result | Ownership effect |
|---|---|---|---|---|
| Request bytes | Indeterminate |
| Result owns a block |
| Request an array | Zero-initialized |
| Result owns a block |
| Resize a block | Added bytes indeterminate | May fail, stay or move | Success replaces old block |
| End object lifetime | Not applicable | No return | Ownership ends |
With four-byte integers, malloc(4 * sizeof *marks) requests 16 bytes, and no element may be read before assignment. calloc(6, sizeof *seen) requests 24 bytes and yields {0, 0, 0, 0, 0, 0} after success; seen[2] = 1 makes {0, 0, 1, 0, 0, 0}. Then call free(seen);. free(NULL) is permitted, but a second free of the same live address is not.
Include <stdint.h> and reject count > SIZE_MAX / sizeof *marks before multiplying. Allocate, check against NULL, then initialize. sizeof *marks follows the pointed-to type, and C needs no malloc() cast.

Worked example: allocate four marks and grow them to six
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t count = 4;
if (count > SIZE_MAX / sizeof(int)) {
return EXIT_FAILURE;
}
int *marks = malloc(count * sizeof *marks);
if (marks == NULL) {
return EXIT_FAILURE;
}
int initial[4] = {72, 85, 91, 68};
for (size_t i = 0; i < count; ++i) {
marks[i] = initial[i];
}
size_t new_count = 6;
if (new_count > SIZE_MAX / sizeof *marks) {
free(marks);
return EXIT_FAILURE;
}
int *grown = realloc(marks, new_count * sizeof *marks);
if (grown == NULL) {
free(marks);
return EXIT_FAILURE;
}
marks = grown;
marks[4] = 77;
marks[5] = 88;
count = new_count;
int total = 0;
for (size_t i = 0; i < count; ++i) {
total += marks[i];
}
printf("count=%zu total=%d average=%.2f\n",
count, total, total / (double)count);
free(marks);
marks = NULL;
return EXIT_SUCCESS;
}First, {72, 85, 91, 68} totals 72 + 85 + 91 + 68 = 316. Four elements request 16 bytes under the teaching assumption, but the portable code does not hard-code 16. After growth, only the added cells receive 77 and 88: 316 + 77 + 88 = 481, and 481 / 6.0 = 80.1666..., printed as:
count=6 total=481 average=80.17Safe realloc code separates success from failure
On a successful positive resize, realloc() preserves the first min(old_size, new_size) bytes. Added bytes are uninitialized, and the block may stay or move. Assign marks = grown and treat old aliases as invalid.
For this failed non-zero resize, grown == NULL leaves the original block live and owned by marks. The example frees it before exit. Direct assignment is unsafe because failure loses the owner and leaks the block. Handle zero explicitly with free(), and assign added cells before reading.

Ownership and cleanup prevent lifetime errors
Ownership is a discipline, not a C keyword. marks owns one allocation; successful realloc() transfers ownership to its result, and free(marks) ends it. Setting marks = NULL cannot repair copied aliases.
After int *alias = marks; free(marks); marks = NULL;, alias is dangling too. alias[0] cannot recover 72. There is no leak, but that access is invalid.
For two resources, start with int *marks = NULL; char *label = NULL;. If label owns 12 bytes and marks owns 24, route all exits through free(marks); free(label);, including partial failure.
Common dynamic-allocation errors and repairs
Faulty code | Why it happens | What goes wrong | Repair |
|---|---|---|---|
| Convenience | Leak on failure | Use a temporary |
Read fresh | Allocation seems initialized | Indeterminate read | Assign first |
| Address remains | Use after free | Stop access |
Call | Cleanup overlaps | Double free | One exit path |
Return without cleanup | Failure path forgotten | Leak | Common cleanup |
A six-element block has indices 0 to 5; marks[6] = 99 is out of bounds. With an 8-byte pointer and 4-byte int, malloc(6 * sizeof marks) requests 48 bytes instead of 24. malloc(6 * sizeof(char)) requests only 6. Use 6 * sizeof *marks.
Compile with cc -std=c11 -Wall -Wextra -Wpedantic -g dma.c -o dma. If supported, add -fsanitize=address,undefined and test a faulty bounds version. Classify reports by size, initialization, bounds, lifetime or ownership. One clean run does not prove every path correct.
How assessments test dynamic allocation
Assessments ask you to calculate bytes under a stated sizeof, compare initialization, trace indices and realloc() failure, or find leaks and dangling pointers. A complete trace should state the requested byte count, initialized range, live owner and valid indices after each operation.
After successful calloc(3, sizeof *p); p[1] = 9;, values are {0, 9, 0} and total 9. With sizeof(int) == 4, malloc(5 * sizeof *q) requests 20 bytes, none readable before initialization. Dynamic storage can implement stacks and queues; per-node allocation and recursive cleanup lead into binary trees and binary search trees.
Dynamic memory allocation in C: the short version
Remember six rules: validate count; size with sizeof *pointer; check allocation; initialize before reading; resize through a temporary; release each owned block once. The path is 4 elements -> 16 bytes -> 6 elements -> 24 bytes -> total 481 -> average 80.17.
For practice, grow {14, 28, 42} from capacity 3 to 5 and append 56, 70. Expect 14 + 28 + 42 + 56 + 70 = 210 and 210 / 5.0 = 42.00, with the same guard, temporary pointer and final free().
Use the C Language Course: Concepts, MCQs and Coding for a structured C path, or Coding for Placements: C, C++, Java and Python for broader problem-solving practice. Compile the program, force every manually reachable cleanup path, and explain who owns the allocation after each line.




