C Programming

Module 2· 8 min read· 10 Questions·completed

2. Control Flow, Storage Classes & Scope

Scope vs Visibility vs Lifetime, storage classes (auto, register, static, extern), internal vs external linkage, switch fallthrough mechanics, and loop control traps in C

GATE CS & UGC NET JRF Core Subject Exam Weightage: 2–3 Direct Questions on static local variables inside nested loops/functions, switch fall-through quirks, and linkage differences between static and extern.


1. Technical Jargon & Core Concepts#

Variable management in C is dictated by three orthogonal properties:

Diagram / Text
+—————————————————————————————————————————————————————————————————————————+
| Scope:     WHERE in the code the variable identifier is recognized.     |
| Visibility:WHERE the identifier is accessible without being shadowed.   |
| Lifetime:  HOW LONG the allocated memory remains valid in execution.    |
| Linkage:   WHETHER the same identifier refers to the same object across |
|            different translation units (.c files).                      |
+—————————————————————————————————————————————————————————————————————————+
  1. Scope Levels:
    • Block Scope: Declared inside a block ({ ... }). Visible from declaration to closing brace.
    • File Scope: Declared outside all blocks. Visible from declaration point to end of the translation unit.
    • Function Scope: Applies exclusively to labels for goto. Labels are visible everywhere inside their enclosing function.
    • Function Prototype Scope: Parameter names in forward declarations; discarded immediately after the prototype ends.
  2. Linkage Types:
    • External Linkage: Identifier can be referenced across multiple translation units (global variables without static, regular functions, extern).
    • Internal Linkage: Identifier can be accessed only within the translation unit where it is defined (file-scope static variables and functions).
    • No Linkage: Local variables, formal parameters, and typedef aliases; each declaration denotes a unique entity.
  3. Storage Duration (Lifetime):
    • Automatic: Allocated when the block is entered; deallocated when the block is exited. Stored on the runtime Call Stack.
    • Static: Allocated at program startup in the data segment; persists throughout the entire program execution until process termination.
    • Allocated: Dynamically allocated and freed via heap allocators (malloc / free).
    • Thread: Introduced in C11 (_Thread_local); persists for the duration of a thread.

2. Storage Classes in C#

C defines four primary storage class specifiers: auto, register, static, and extern.

Storage ClassStorage RegionDefault ValueLifetimeScopeLinkage
autoStackGarbage (indeterminate)Block executionBlockNone
registerCPU Register (or Stack)Garbage (indeterminate)Block executionBlockNone
static (local)Data Segment (.data / .bss)Zero (0)Entire programBlockNone
static (global)Data Segment (.data / .bss)Zero (0)Entire programFileInternal
externData Segment (.data / .bss)Zero (0)Entire programGlobalExternal

2.1 The register Storage Class & Address-of Restriction#

The register keyword advises the compiler that the variable will be heavily accessed, requesting placement in a CPU register for speed.

[!CRITICAL] GATE Exam Trap: Address-of Operator & on Register Variables! Because CPU registers do not have memory addresses in RAM, taking the address of a register variable using the address-of operator (&) is a COMPILE-TIME ERROR, even if the compiler placed it in RAM!

C
register int x = 10;
int *p = &x; // COMPILE ERROR: address of register variable 'x' requested

2.2 The Dual Nature of static#

The keyword static has two entirely different meanings depending on where it appears:

Diagram / Text
                              Dual Meanings of 'static'
                                         │
                 ┌───────────────────────┴───────────────────────┐
                 ▼                                               ▼
     Local Variable Scope                            File Scope (Global)
  Persists across function calls                  Restricts linkage to Internal
  Initialized exactly ONCE at compile-time        Hides variable/function from other
  Preserves previous mutated state                translation units (Encapsulation)

Tricky GATE Question: Local Static in Recursive Functions#

C
#include <stdio.h>

int f(int n) {
    static int x = 0; // Initialized ONCE before program starts
    if (n <= 0) return 1;
    x++;
    return f(n - 1) + x;
}

int main(void) {
    printf("%d\n", f(3)); // What is the output?
    return 0;
}

Step-by-step Trace:

  1. f(3): x becomes 1. Calls f(2) + x.
  2. f(2): x becomes 2. Calls f(1) + x.
  3. f(1): x becomes 3. Calls f(0) + x.
  4. f(0): Base case returns 1.
  5. Notice: When returning and unwinding the call stack, what is the value of x? Since x is static and shared, its value is 3 across all stack frames!
    • f(1) returns 1+3=41 + 3 = 4.
    • f(2) returns 4+3=74 + 3 = 7.
    • f(3) returns 7+3=107 + 3 = 10. Output: 10!

2.3 extern: Declaration vs. Definition#

C
// File: module.h
extern int counter; // DECLARATION: informs compiler 'counter' exists elsewhere. No memory allocated!

// File: module.c
int counter = 0;    // DEFINITION: allocates memory in .bss/.data segment.
Note

Tentative Definitions in C: If a file-scope declaration has no storage class specifier or has static, and has no initializer, it is a tentative definition.

C
int x; // Tentative definition (valid C, initialized to 0 if no other definition exists)
int x; // Legal in C! Redundant tentative definition merged into one object.

3. Control Flow Mechanics & Traps#

3.1 The switch Statement & Fallthrough#

The switch statement evaluates an integral control expression and jumps directly to the matching case label.

C
#include <stdio.h>

int main(void) {
    int x = 2;
    switch (x) {
        case 1: printf("1 ");
        case 2: printf("2 "); // Matches here!
        case 3: printf("3 "); // No break! Falls through!
        default: printf("D ");// Falls through into default!
    }
    return 0;
}
// Output: 2 3 D

Key Syntax Constraints:#

  1. Integer Types Only: switch(expr) requires an integral type (int, char, short, long, enum). Floating-point numbers (float, double) and strings are illegal.
  2. Compile-Time Constant Case Expressions: Every case value must be an integral constant expression (e.g., case 3: or case 'A': or case 2 + 3:). Variables like case x: are illegal.
  3. Labels as Jump Targets: case labels are conceptually jump labels (similar to goto). This allows unusual but valid constructs like Duff's Device.

3.2 Duff's Device: Loop Unrolling via Switch#

In 1983, Tom Duff created a famous loop-unrolling mechanism that illustrates the true nature of switch in C:

C
// Copy 'count' elements from 'from' to 'to' unrolled by a factor of 8
void send(int *to, int *from, int count) {
    int n = (count + 7) / 8;
    switch (count % 8) {
        case 0: do { *to = *from++;
        case 7:      *to = *from++;
        case 6:      *to = *from++;
        case 5:      *to = *from++;
        case 4:      *to = *from++;
        case 3:      *to = *from++;
        case 2:      *to = *from++;
        case 1:      *to = *from++;
                } while (--n > 0);
    }
}

3.3 Loop Evaluation Quirks: break vs. continue#

Control StatementEffect in for loopEffect in while loopEffect in switch
breakTerminates loop immediately, jumps past loop bodyTerminates loop immediately, jumps past loop bodyExits switch block
continueSkips remaining body, jumps to increment expression (step)Skips remaining body, jumps directly to condition checkILLEGAL unless enclosed within a loop
C
// Classic Trap: Infinite loop caused by continue in while loop
int i = 0;
while (i < 5) {
    if (i == 3) {
        continue; // Jumps to 'while (i < 5)', SKIPPING 'i++'! Infinite loop!
    }
    printf("%d ", i);
    i++;
}

4. Best Practices & Defensive Coding#

  1. Always Annotate Intentional Fallthrough: In modern C, annotate intentional fallthrough using /* fallthrough */ comments or the C23 attribute [[fallthrough]] to silence compiler warnings.
  2. Every switch Must Have a default Clause: Even if all expected cases are covered, a default: assert(0); protects against future enum expansions or corrupted state.
  3. Limit Variable Scope (Declare at Point of Use): Minimize the visibility lifetime of variables to reduce bug surface.
  4. Use static on Global Helper Functions: Mark all functions that are private to a file as static to prevent global namespace pollution and allow link-time optimization (inlining).

Practice Quiz

Test your understanding with step-by-step solutions

Practice Quiz

10 questions · 90s per question

Each question has a 90-second time limit. Unanswered questions will be auto-submitted when time runs out.