Dynamic memory allocation, macros, scoping and file handling often arrive in the same C syllabus, but they act at different stages. Macros transform tokens before compilation, scope decides which declaration a name denotes, allocation manages storage at runtime, and file handling moves data through streams. That mix is why one trace can feel like four unrelated puzzles. The C Programming by Yash Sir: Worked Examples & Traces maps the full language. For these four topics, track token expansion, lexical binding, storage ownership and stream status separately.
Put the Four Topics on One C Execution Map
Track the kind of state that belongs to each stage.
Topic | Stage | State to track | Typical failure |
|---|---|---|---|
Macro | Preprocessing | Token expansion | Precedence or repeated evaluation |
Scoping | Translation and execution | Name binding | Shadowing the intended declaration |
DMA | Runtime | Owner, size and lifetime | Leak or dangling pointer |
Files | Runtime | Stream, mode, position and return status | Bad read or truncation |
Scope is name visibility. Storage duration is object lifetime. Ownership is the duty to release allocated storage. They are separate. In particular, static in a declaration can affect storage duration or linkage, but it does not mean static, or lexical, scoping.
Dynamic Memory Allocation: Allocate, Grow, Use and Free
malloc(n) obtains uninitialised allocated storage. calloc(count, size) allocates and zero-initialises the requested bytes. realloc(ptr, new_size) may grow the block in place or move it. free(ptr) ends that allocation's lifetime. Production code must also check that a count cannot overflow before multiplying it by an element size.
A temporary pointer preserves ownership whether realloc succeeds or fails:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *scores = malloc(4 * sizeof *scores);
if (scores == NULL) {
return EXIT_FAILURE;
}
scores[0] = 12;
scores[1] = 18;
scores[2] = 25;
scores[3] = 31;
int *tmp = realloc(scores, 6 * sizeof *scores);
if (tmp == NULL) {
free(scores);
return EXIT_FAILURE;
}
scores = tmp;
scores[4] = 40;
scores[5] = 47;
int sum = 0;
for (int i = 0; i < 6; ++i) {
sum += scores[i];
}
printf("%d\n", sum);
free(scores);
return EXIT_SUCCESS;
}The running sum is 12 + 18 = 30, 30 + 25 = 55, 55 + 31 = 86, 86 + 40 = 126, and 126 + 47 = 173. The new elements must be assigned before reading because realloc does not initialise the added region. Directly writing scores = realloc(scores, ...) can replace the only owner with NULL on failure, making the old allocation unreachable. The temporary pointer preserves that owner until success, then free(scores) runs exactly once.

Macros: Trace the Expansion Before Evaluating It
The unsafe and corrected definitions show the difference:
Unsafe macro | Corrected macro |
|---|---|
|
|
Expansion comes before arithmetic. BAD_SQUARE(2+4) becomes 2+4*2+4; multiplication gives 2+8+4, so the result is 14. SQUARE(2+4) becomes ((2+4) * (2+4)); each sum is 6, so the result is 36.
Parentheses do not fix repeated side effects. SQUARE(i++) modifies i more than once without sequencing, so it has no valid numeric answer. Use side-effect-free arguments, or use static inline int square_int(int x) { return x * x; } when type checking and single evaluation matter. Object-like macros can name tokens such as #define BUFFER_CAPACITY 64, while #ifdef DEBUG selects code conditionally. A macro is token replacement, not a function, and it does not follow ordinary block scope.
Scoping and Storage Duration: Resolve Each Name Lexically
Standard C uses lexical, also called static, scope. Resolve a use of a name from where the function is defined, not from the function that called it.
#include <stdio.h>
int x = 5;
void show(void) {
printf("%d\n", x);
}
int main(void) {
int x = 20;
show();
{
int x = 8;
printf("%d\n", x);
}
printf("%d\n", x);
return 0;
}The output is 5, then 8, then 20, on separate lines. show denotes the file-scope x = 5; it cannot see its caller's x = 20. Under a hypothetical dynamic-scoping rule, the first line would instead be 20.
Now separate scope from lifetime. In static int calls = 0; inside a function, the name has block scope, but its object has static storage duration and retains its value between calls. A file-scope static name has internal linkage, which is a third axis. A macro has textual reach from its #define to a matching #undef or the end of the preprocessing translation unit. Variable-shadowing rules do not apply to it.
File Handling: Read Two Records and Write a Checked Summary
Assume records.txt contains these complete records:
id | name | marks |
|---|---|---|
17 | Asha | 72 |
23 | Ravi | 81 |
The read operation, not an EOF guess, controls the loop:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *in = fopen("records.txt", "r");
if (in == NULL) {
perror("records.txt");
return EXIT_FAILURE;
}
int id, marks, count = 0, total = 0, scan_status;
char name[32];
while ((scan_status = fscanf(in, "%d %31s %d", &id, name, &marks)) == 3) {
++count;
total += marks;
}
int read_failed = ferror(in);
int malformed = !read_failed && scan_status != EOF;
int input_close_failed = fclose(in) == EOF;
if (read_failed || malformed || input_close_failed || count == 0) {
return EXIT_FAILURE;
}
FILE *out = fopen("summary.txt", "w");
if (out == NULL) {
perror("summary.txt");
return EXIT_FAILURE;
}
double average = (double) total / count;
int write_failed = fprintf(out, "count=%d\naverage=%.2f\n",
count, average) < 0;
if (fclose(out) == EOF) {
write_failed = 1;
}
return write_failed ? EXIT_FAILURE : EXIT_SUCCESS;
}After row one, the state is (count=1, total=72). After row two, it is (count=2, total=153) because 72 + 81 = 153. Therefore 153 / 2 = 76.5, printed as 76.50. summary.txt contains count=2 and average=76.50 on separate lines.
while (!feof(in)) is wrong because EOF becomes set only after a read attempt fails. Test fscanf instead, check ferror, and close the stream. Mode r reads, w writes after truncating existing content, and a writes at the end. Their binary forms are rb, wb and ab; text and binary streams need not behave identically on every platform.

Failure Patterns and Their Repairs
Failure | Repair |
|---|---|
Overwrite the owner with direct | Store the result in a temporary pointer |
Read uninitialised or freed storage | Initialise before reading and maintain one clear owner |
Omit macro parentheses or pass | Parenthesise fully, use side-effect-free arguments, or call an inline function |
Confuse scope with storage duration | Track visibility and lifetime separately |
Use | Drive the loop from the input result, check every open, and remember that |
A double free and any dereference after free are invalid operations, not style problems. For files, a checked return status is part of the data flow.
How GATE and Interviews Test This Cluster
Common task shapes are predictable even though no frequency claim is needed: expand a macro and apply precedence, predict which declaration a name denotes, find a leak or dangling pointer, or count the records accepted by a stream loop. The MCQ, MSQ or NAT GATE question types guide helps place such traces in their answer formats, while GATE course and preparation guidance provides a wider study sequence.
Use this rapid check: the safe square of 2+4 is 36; show() prints 5; the six-element allocation sums to 173; and the file loop accepts 2 records with average 76.50. Before choosing an answer, write the intermediate expansion, binding, owner or return status.
The Short Version and the Next Practice Step
Own allocated memory: preserve its only owner until realloc succeeds. Expand macros: parenthesise replacements and avoid side effects. Bind names lexically: keep scope, linkage and storage duration separate. Check stream operations: drive every loop from the read result.
For a full lesson and practice sequence, use the C Language Course. Then use the wider GATE category for subject-level preparation. Re-run all four traces on paper without looking at the answers.




