Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Storage Classes & Scope
C

Storage Classes & Scope

Practice 11 questions covering storage classes, variable scope, lifetime, linkage, and related C language concepts.

1. What is the purpose of the volatile keyword in C variable declarations?

Answer: It tells the compiler the variable can change outside the program’s control, forcing a fresh memory read/write on every access instead of caching.

volatile prevents the compiler from assuming it knows the variable’s value. Without it, the compiler may keep a copy in a register or optimize away repeated reads (since “nothing else could change it”). With volatile, every access goes to the actual memory location.

Why it matters — the value genuinely changes from the program’s perspective:

  • Memory-mapped hardware registers (device I/O).
  • Variables modified by signal handlers.
  • Shared state updated by another thread or by interrupt-driven code.
volatile uint8_t *status = (volatile uint8_t *)0x4000;  // hardware register
while (*status & BUSY) { /* poll */ }   // must re-read every iteration

Note: volatile is about reads and writes not being optimized away — it is not about atomicity or thread-safety. For concurrent threads you still need synchronization. The interview answer: volatile forces direct memory access on every read/write, preventing the compiler from caching or eliding accesses to values that change externally.

Answer:

It tells the compiler the variable can change outside the program’s control, forcing a fresh memory read/write on every access instead of caching.

volatile prevents the compiler from assuming it knows the variable’s value. Without it, the compiler may keep a copy in a register or optimize away repeated reads (since “nothing else could change it”). With volatile, every access goes to the actual memory location.

Why it matters — the value genuinely changes from the program’s perspective:

  • Memory-mapped hardware registers (device I/O).
  • Variables modified by signal handlers.
  • Shared state updated by another thread or by interrupt-driven code.
volatile uint8_t *status = (volatile uint8_t *)0x4000;  // hardware register
while (*status & BUSY) { /* poll */ }   // must re-read every iteration

Note: volatile is about reads and writes not being optimized away — it is not about atomicity or thread-safety. For concurrent threads you still need synchronization. The interview answer: volatile forces direct memory access on every read/write, preventing the compiler from caching or eliding accesses to values that change externally.

2. What does the static keyword mean when applied to a global variable declared outside any function?

Answer: It gives the variable internal linkage — its scope is restricted to the translation unit (source file) where it’s declared.

At file scope, static changes linkage, not storage (a file-scope variable is already static-storage-duration). With static, the variable’s name is only visible within its own translation unit; it won’t collide with same-named symbols in other source files at link time.

// in file1.c
static int counter = 0;   // only file1.c sees this
  • Without static: extern linkage — the name is visible program-wide (though const globals have internal linkage by default in C++ but not C).
  • With static: internal linkage — private to the file. This is the standard way to make file-local state and helper globals that shouldn’t leak into the global namespace.

Note this is different from static inside a function, which means “initialized once, persists between calls.” The interview answer: at global scope, static restricts the variable’s linkage to its own translation unit (file), preventing external symbol collisions.

Answer:

It gives the variable internal linkage — its scope is restricted to the translation unit (source file) where it’s declared.

At file scope, static changes linkage, not storage (a file-scope variable is already static-storage-duration). With static, the variable’s name is only visible within its own translation unit; it won’t collide with same-named symbols in other source files at link time.

// in file1.c
static int counter = 0;   // only file1.c sees this
  • Without static: extern linkage — the name is visible program-wide (though const globals have internal linkage by default in C++ but not C).
  • With static: internal linkage — private to the file. This is the standard way to make file-local state and helper globals that shouldn’t leak into the global namespace.

Note this is different from static inside a function, which means “initialized once, persists between calls.” The interview answer: at global scope, static restricts the variable’s linkage to its own translation unit (file), preventing external symbol collisions.

3. What is the scope and lifetime of a local variable declared with static inside a function body?

Answer: Block scope (only the function can see it), but lifetime spans the whole program — it keeps its value between calls and is initialized once.

A local static variable combines two properties:

  • Scope: block/function scope — accessible only inside the function where it’s declared (same visibility rules as a normal local).
  • Lifetime: static storage duration — it lives for the entire program run, not just the function call. It’s initialized once (before the program starts), and retains its value across calls.
int next() {
    static int counter = 0;   // initialized once
    return counter++;
}

Each call to next() returns an increasing value — the counter survives between calls. That’s the classic use: persistent state without a global. The interview answer: block scope, program-long lifetime, initialized once, value preserved between calls.

Answer:

Block scope (only the function can see it), but lifetime spans the whole program — it keeps its value between calls and is initialized once.

A local static variable combines two properties:

  • Scope: block/function scope — accessible only inside the function where it’s declared (same visibility rules as a normal local).
  • Lifetime: static storage duration — it lives for the entire program run, not just the function call. It’s initialized once (before the program starts), and retains its value across calls.
int next() {
    static int counter = 0;   // initialized once
    return counter++;
}

Each call to next() returns an increasing value — the counter survives between calls. That’s the classic use: persistent state without a global. The interview answer: block scope, program-long lifetime, initialized once, value preserved between calls.

4. What is the value of an uninitialized local automatic variable inside a function body?

Answer: Indeterminate (garbage) — whatever bit pattern happens to be in that stack memory; reading it before writing is undefined behavior.

Local (automatic) variables are not zero-initialized. They contain whatever leftover data occupied that stack location — stale values from previous frames. The exact content is unpredictable.

void f() {
    int x;          // indeterminate value
    printf("%d", x);  // UB: reading an uninitialized variable
}

Reading an uninitialized automatic variable is undefined behavior in C (for non-character types; even char uninitialized reads are UB before assignment in practice, though unsigned char/char are special-cased for indeterminate reads). The value is never guaranteed to be 0, NULL, or anything specific.

Contrast: static and global variables are zero-initialized. The interview answer: indeterminate garbage from the stack; reading before assignment is undefined behavior.

Answer:

Indeterminate (garbage) — whatever bit pattern happens to be in that stack memory; reading it before writing is undefined behavior.

Local (automatic) variables are not zero-initialized. They contain whatever leftover data occupied that stack location — stale values from previous frames. The exact content is unpredictable.

void f() {
    int x;          // indeterminate value
    printf("%d", x);  // UB: reading an uninitialized variable
}

Reading an uninitialized automatic variable is undefined behavior in C (for non-character types; even char uninitialized reads are UB before assignment in practice, though unsigned char/char are special-cased for indeterminate reads). The value is never guaranteed to be 0, NULL, or anything specific.

Contrast: static and global variables are zero-initialized. The interview answer: indeterminate garbage from the stack; reading before assignment is undefined behavior.

5. What is the memory location where uninitialized global and static variables are stored?

Answer: The BSS segment (Block Started by Symbol).

A program’s memory layout separates data by initialization state:

  • Text/Code — executable instructions.
  • Data segmentinitialized globals/statics.
  • BSSuninitialized globals and statics. The OS zeroes BSS before program start, so uninitialized globals reliably read as 0.
  • Heap — dynamic allocation (grows toward higher addresses).
  • Stack — function call frames (grows toward lower addresses).
int g;            // BSS — zeroed before main
static int s;     // BSS — zeroed before main

That’s why globals/statics are guaranteed 0 while automatic (local) variables are garbage. The interview answer: uninitialized globals/statics live in BSS, which the OS zeroes before execution starts.

Answer:

The BSS segment (Block Started by Symbol).

A program’s memory layout separates data by initialization state:

  • Text/Code — executable instructions.
  • Data segmentinitialized globals/statics.
  • BSSuninitialized globals and statics. The OS zeroes BSS before program start, so uninitialized globals reliably read as 0.
  • Heap — dynamic allocation (grows toward higher addresses).
  • Stack — function call frames (grows toward lower addresses).
int g;            // BSS — zeroed before main
static int s;     // BSS — zeroed before main

That’s why globals/statics are guaranteed 0 while automatic (local) variables are garbage. The interview answer: uninitialized globals/statics live in BSS, which the OS zeroes before execution starts.

6. What does the expression (void)var; do in C source files?

Answer: It suppresses “unused variable” compiler warnings intentionally, without emitting any runtime code.

Writing (void)var; as a statement casts var to void and discards it. The compiler sees the variable as “used” (it appears in an expression), so it won’t warn about it being unused — but casting to void means “I’m deliberately ignoring this,” and no machine code is generated for it.

Typical uses:

  • Function parameters that are intentionally unused (e.g., callback signatures where you don’t need every argument).
  • Suppressing warnings in macro-generated code.
  • Documenting that a variable is intentionally ignored.

It’s purely a compile-time signal to the compiler and a readability note to humans. The interview answer: an intentional no-op that silences unused-variable warnings without runtime cost.

Answer:

It suppresses “unused variable” compiler warnings intentionally, without emitting any runtime code.

Writing (void)var; as a statement casts var to void and discards it. The compiler sees the variable as “used” (it appears in an expression), so it won’t warn about it being unused — but casting to void means “I’m deliberately ignoring this,” and no machine code is generated for it.

Typical uses:

  • Function parameters that are intentionally unused (e.g., callback signatures where you don’t need every argument).
  • Suppressing warnings in macro-generated code.
  • Documenting that a variable is intentionally ignored.

It’s purely a compile-time signal to the compiler and a readability note to humans. The interview answer: an intentional no-op that silences unused-variable warnings without runtime cost.

7. What is the default access modifier of global variables in C across multiple translation units if declared without static?

Answer: External linkage — the variable is visible program-wide, and other translation units can access it with extern.

A file-scope variable declared without static has external linkage by default:

// file1.c
int counter = 0;            // external linkage
// file2.c
extern int counter;         // refers to file1.c's counter
counter++;

That’s what makes a name usable across translation units. Adding static flips it to internal linkage, hiding the variable from other files. (Contrast C++: a const file-scope variable has internal linkage by default in C++, but not in C — in C, const globals are still external by default unless marked static.) The interview answer: external linkage by default; other translation units reach it via extern.

Answer:

External linkage — the variable is visible program-wide, and other translation units can access it with extern.

A file-scope variable declared without static has external linkage by default:

// file1.c
int counter = 0;            // external linkage
// file2.c
extern int counter;         // refers to file1.c's counter
counter++;

That’s what makes a name usable across translation units. Adding static flips it to internal linkage, hiding the variable from other files. (Contrast C++: a const file-scope variable has internal linkage by default in C++, but not in C — in C, const globals are still external by default unless marked static.) The interview answer: external linkage by default; other translation units reach it via extern.

8. What happens when compiling an undefined variable reference in C without declaring it extern or local?

Answer: A compile error — “Undeclared identifier” — or a link error (“unresolved external symbol”) if the declaration exists but the definition doesn’t.

C requires every identifier to be declared before use. If you reference a variable that was never declared:

  • Compile time: “use of undeclared identifier” (or implicit-declaration error).

If you declare it (extern int counter;) but never define it anywhere (no int counter; in any translation unit), then the linker fails: “unresolved external symbol.”

extern int counter;   // declared but never defined anywhere
counter = 1;          // link error: unresolved external

So: missing declaration → compiler error; declaration without definition → linker error. The interview answer: a compile error (undeclared identifier) or a link error (unresolved external symbol) for a declared-but-never-defined variable.

Answer:

A compile error — “Undeclared identifier” — or a link error (“unresolved external symbol”) if the declaration exists but the definition doesn’t.

C requires every identifier to be declared before use. If you reference a variable that was never declared:

  • Compile time: “use of undeclared identifier” (or implicit-declaration error).

If you declare it (extern int counter;) but never define it anywhere (no int counter; in any translation unit), then the linker fails: “unresolved external symbol.”

extern int counter;   // declared but never defined anywhere
counter = 1;          // link error: unresolved external

So: missing declaration → compiler error; declaration without definition → linker error. The interview answer: a compile error (undeclared identifier) or a link error (unresolved external symbol) for a declared-but-never-defined variable.

9. What does the storage class register suggest to the compiler?

Answer: A hint that the variable is heavily accessed and should be kept in a CPU register — though modern compilers generally ignore it.

register tells the compiler “this variable is used often; put it in a register if you can.” In modern optimizing compilers, register allocation is fully automatic, so register is essentially ignored as a hint. Remaining semantic restrictions:

  • You cannot take the address (&var) of a register variable (since it may have no memory address).
  • It applies only to block-scope (and function-parameter) variables, not globals.

The keyword survives mostly for historical/portability reasons and is now largely vestigial in practice. The interview answer: a mostly-ignored hint that the variable should live in a register; taking its address is illegal.

Answer:

A hint that the variable is heavily accessed and should be kept in a CPU register — though modern compilers generally ignore it.

register tells the compiler “this variable is used often; put it in a register if you can.” In modern optimizing compilers, register allocation is fully automatic, so register is essentially ignored as a hint. Remaining semantic restrictions:

  • You cannot take the address (&var) of a register variable (since it may have no memory address).
  • It applies only to block-scope (and function-parameter) variables, not globals.

The keyword survives mostly for historical/portability reasons and is now largely vestigial in practice. The interview answer: a mostly-ignored hint that the variable should live in a register; taking its address is illegal.

10. What is the primary operational issue with static variables declared inside functions in multithreaded C programs?

Answer: A function-local static variable is a single shared instance across all threads — concurrent access with a write causes data races unless synchronized.

Despite being declared inside a function, a static local lives in the program’s data segment, shared by every thread that calls the function. If two threads call the function and both read/write that variable without synchronization, that’s a data race (undefined behavior in C11’s memory model) — corrupted or inconsistent values.

int next() {
    static int counter = 0;   // ONE copy for all threads
    return counter++;         // race if called concurrently
}

Fixes: protect with a mutex, use C11 atomics (_Atomic), or make it thread-local (C11 _Thread_local/thread_local) if each thread should have its own instance. The interview answer: the static is shared by all threads, causing data races on concurrent access unless mutex/atomic/thread-local is used.

Answer:

A function-local static variable is a single shared instance across all threads — concurrent access with a write causes data races unless synchronized.

Despite being declared inside a function, a static local lives in the program’s data segment, shared by every thread that calls the function. If two threads call the function and both read/write that variable without synchronization, that’s a data race (undefined behavior in C11’s memory model) — corrupted or inconsistent values.

int next() {
    static int counter = 0;   // ONE copy for all threads
    return counter++;         // race if called concurrently
}

Fixes: protect with a mutex, use C11 atomics (_Atomic), or make it thread-local (C11 _Thread_local/thread_local) if each thread should have its own instance. The interview answer: the static is shared by all threads, causing data races on concurrent access unless mutex/atomic/thread-local is used.

11. What does the storage class extern signify when applied to a variable declaration inside a function frame (extern int count;)?

Answer: It declares that count refers to a global variable defined elsewhere — it does not allocate new storage.

extern is a declaration without definition: it tells the compiler “this name refers to storage that exists elsewhere (another file, or a global scope) — don’t allocate memory for it here.” Inside a function, extern int count; lets you reference a file-scope global by name without creating a local copy:

int count;              // global definition in file1.c
// file2.c
void f() {
    extern int count;   // refers to file1.c's global, no new memory
    count++;
}

Contrast a plain local int count; — that allocates a new automatic variable shadowing the global. extern in block scope simply links the name to the external definition, and prevents a separate local allocation. The interview answer: extern references storage defined elsewhere (a global in another scope/file) without allocating new memory.

Answer:

It declares that count refers to a global variable defined elsewhere — it does not allocate new storage.

extern is a declaration without definition: it tells the compiler “this name refers to storage that exists elsewhere (another file, or a global scope) — don’t allocate memory for it here.” Inside a function, extern int count; lets you reference a file-scope global by name without creating a local copy:

int count;              // global definition in file1.c
// file2.c
void f() {
    extern int count;   // refers to file1.c's global, no new memory
    count++;
}

Contrast a plain local int count; — that allocates a new automatic variable shadowing the global. extern in block scope simply links the name to the external definition, and prevents a separate local allocation. The interview answer: extern references storage defined elsewhere (a global in another scope/file) without allocating new memory.

My Private Notes

Notes are auto-saved locally to this device.