Skip to content

Syllabus · CS Computer Science and Information Technology

All topics in this subject
C programming foundations7
Arrays and pointers9
Recursion6
Probability and statistics foundations9
Database systems foundations15
Linear algebra foundations6
Computer networks foundations9
Calculus foundations6
Theory of computation foundations9
Compiler design foundations9
Linked lists, stacks and queues5
Trees, BSTs and heaps4
Graph foundations3
Algorithms foundations15
Discrete mathematics foundations18
Digital logic foundations22
Computer organization foundations12
Operating systems foundations15

C scope, storage duration and allocated ownership

Lesson 16 of 1798 minFree

Objectives and language contract

Separate a name's visibility from an object's lifetime; trace struct pointers; handle allocation failure without losing ownership; detect dangling aliases. Prerequisites are basic C functions and pointers. We use ISO C11/C17 with no assumptions about pointer width, struct padding or allocator address reuse. Code uses <stdlib.h> and, where needed, <stddef.h>/<stdint.h>. Examples concern single-threaded execution. Removal examples assume an acyclic list of distinct live nodes with next either NULL or pointing to the next live node.

1. Scope answers “which declaration?”; duration answers “which object is alive?”

Block scope governs where a local identifier can be used. Automatic storage duration normally keeps the local object alive until execution leaves its block. Static storage duration lasts for the entire program execution, even when the identifier has only block scope. Allocated storage duration lasts from a successful allocation until deallocation, independently of the scope of the pointer variable holding its address. Linkage concerns whether declarations in different scopes/translation units refer to the same entity; it is a separate question.

Worked trace: int next(void) { static int count; int step=2; count+=step; return count; }. Three calls return 2,4,6. count is initialized to zero once and persists; step is a fresh initialized automatic object on each call. Replacing static int count with int count=0 makes every call return 2. An uninitialized automatic int is not implicitly zero; reading its indeterminate value is not a valid deterministic trace and can have undefined behavior. Static arithmetic objects default to zero; static pointers default to a null pointer, not necessarily an all-zero byte representation.

Shadowing changes name lookup, not the hidden object's lifetime: int x=5; { int x=9; x++; } leaves the outer x as 5. The inner x dies on block exit. Returning the address of an ordinary automatic local does not prolong its lifetime. A caller must not dereference that dangling result. A pointer to a block-static object can remain valid after the function returns, although later calls may change that same shared object.

2. Struct values and struct pointers

For struct Node { int value; struct Node next; }; and a valid Node pointer p, p->value means (p).value. Parentheses matter because . binds more tightly than unary *. A null pointer or dangling pointer cannot be used for member access. sizeof(struct Node) need not equal sizeof(int)+sizeof(pointer): alignment may insert padding; do not infer field offsets from that sum.

Struct assignment copies member values, not the entire reachable data structure. If struct Node a has next pointing to node t and struct Node b=a, then a and b are separate struct objects but a.next and b.next initially alias t. Changing b.value does not change a.value. Changing b.next->value changes t and is observable through a.next. This is a shallow copy, not a deep clone. It does not create a second owner entitled to free the same allocation.

3. Allocate before publishing a new node

A robust node constructor is:

struct Node *make(int value, struct Node *next) { struct Node *p = malloc(sizeof *p); if (p == NULL) return NULL; p->value = value; p->next = next; return p; }

sizeof *p obtains the pointed-to struct size without evaluating p for this non-VLA type, even though p has not yet been initialized during the initializer. The expression is not a dereference access. malloc supplies uninitialized storage, so initialize fields before reading them or publishing the pointer. No cast is required for malloc's void in C when the declaration is visible. calloc zeroes bytes but should not be generalized to a portable null-pointer initialization of every pointer member; explicitly assign pointer fields.

To prepend safely, let p=make(7,head); if p!=NULL, assign head=p. If allocation fails, leave head unchanged and report failure. Overwriting head directly with a potentially failing allocator result can lose the only path to existing storage. In allocating n objects, guard multiplication before malloc(n*sizeof *p): for a positive element size require n<=SIZE_MAX/sizeof *p. A wrapped byte count may allocate a smaller buffer than the subsequent loop expects. Keep zero-size allocation cases out of these examples; their return/free details do not establish permission to access an element.

4. Ownership, aliases and deallocation

Ownership is an algorithm contract, not a C type qualifier: specify who must free an allocation exactly once. Borrowed pointers may access a live object but must not outlive it or free it unless ownership is transferred. free(NULL) is valid and does nothing. free requires NULL or a still-live allocation base pointer from a compatible allocator. It must not receive a stack-object address, an interior pointer, or an already-freed allocation.

Worked safe removal: with head owning the first allocated node, save struct Node *old=head; if old!=NULL, set head=old->next, then free(old). Read next before freeing the node, because old->next afterward is a use-after-free. A separate observer alias to old becomes dangling too. Setting old=NULL after free changes only old; it does not repair other aliases. Do not read, compare or dereference stale pointer values as part of these portable traces after the pointed-to object's lifetime ends. No assumption that the old address will remain unused is valid.

For a positive new size, use a temporary with realloc: tmp=realloc(p,newbytes); if tmp==NULL, p still owns its old allocation; otherwise assign p=tmp and use the returned pointer. On success the old allocation's lifetime ends, even if the returned address numerically looks unchanged; aliases must be reconsidered. On failure the original remains unchanged. Direct p=realloc(p,newbytes) risks losing the sole pointer on failure. Freeing an outer struct does not automatically free separately allocated children; follow the chosen ownership graph, taking care not to free shared nodes twice.

Reasoning checklist

For each name write its declaration and scope. For each object write duration, initialization, owner and live aliases. On every return/free/realloc update lifetimes before evaluating the next access. A leak means owned storage became unreachable without deallocation; a dangling pointer means an alias outlived its object. They are different bugs and can coexist.

Original practice: six single-correct MCQs

These original teaching questions are not previous-year questions. Choose one answer.

P01. A function has static int c; then c+=2; return c;. First three calls return:

A. 2,2,2 B. 2,4,6 C. Indeterminate values D. 0,2,4

Answer: B

A: That describes a freshly zero-initialized automatic c, not static c.

B: c begins at zero once and persists between calls.

C: Static c has defined zero initialization.

D: The increment occurs before each return.

P02. For struct Node b=a, with a.next pointing to live node t, which is guaranteed?

A. b.next points to a new clone of t B. b and a are the same struct object C. b.next and a.next initially point to the same t D. Both pointers separately own t for freeing

Answer: C

A: Struct assignment does not clone reachable allocations.

B: Assignment copies values into a distinct object.

C: Pointer member values are copied, creating aliases.

D: Ownership is not duplicated by a shallow copy.

P03. After malloc(sizeof *p) returns NULL, the constructor should:

A. Write p->value before checking B. Free the caller’s existing list unconditionally C. Treat NULL as an empty allocated node D. Report failure without dereferencing p

Answer: D

A: A NULL member access is invalid.

B: Allocation failure does not authorize destruction of existing state.

C: NULL represents failure here, not an object.

D: Check the result before accessing any fields.

P04. Which is a valid argument to free?

A. NULL B. Address of an automatic local int C. Pointer one element inside a live malloc allocation D. A nonnull pointer to an already-deallocated allocation

Answer: A

A: free(NULL) is a defined no-op.

B: Automatic storage was not obtained from the allocator.

C: An interior pointer is not the allocation base.

D: Double free is undefined behavior.

P05. For positive size, tmp=realloc(p,n) fails and returns NULL. What happens to the original allocation?

A. It is always freed B. It remains live and unchanged through p C. It is zero-filled D. Its size definitely becomes n

Answer: B

A: Failure preserves the original allocation.

B: The temporary keeps p available for recovery or freeing.

C: Failure does not initialize the old contents.

D: The requested resize did not succeed.

P06. Which sequence safely removes a nonnull allocated head node?

A. free(head); head=head->next; B. old=head; free(old); head=old->next; C. old=head; head=old->next; free(old); D. head=head->next; free(head);

Answer: C

A: It reads a freed node.

B: It also reads old->next after free.

C: Save the link before ending the old node’s lifetime.

D: It loses the old node and either frees a nonnull successor or performs free(NULL), a no-op.

Sources and scope

GATE CS 2026 bounded extension of existing foundations. Not a whole-subject completion claim. Examples and questions are original.

Sign in to keep your progress. Sign in