optional · CSS study notes
II.9 Global, Local and Shared Variables (2017)
⬆️ P-I.II. Programming Fundamentals · Question Bank · Chapter II (2016–2026)
🧠
Remember: Scope = where; lifetime = how long; linkage = which declaration.
Start here — what you should be able to do
- Trace shadowing without assuming the outer value was overwritten.
- Compare automatic locals, static locals and globals.
- Explain extern and shared-state synchronization.
Reading order: foundation checks → full notes and examples → exam traps → self-test without notes → past-paper answers. All sections below remain part of the topic; code examples are kept in full.
Foundation checks
Three questions for every variable
| Question | Example |
|---|---|
| Where can the name be used? | A static local's name stays inside its block. |
| How long does storage last? | A static local persists between calls. |
| Do declarations denote the same entity? | An extern declaration can refer to one definition elsewhere. |
- Modern C++
autonormally means type deduction; automatic storage duration is a separate concept. - A global is not automatically visible everywhere: declarations, scope and linkage matter.
externdoes not require the definition to be in another file.- Unsynchronised conflicting accesses to the same non-atomic object can form a C++ data race: this is undefined behaviour, not merely a guaranteed lost update.
1. Local Variables
- Declared inside a function/block
- Scope limited to that function/block only — inaccessible outside it
- Lifetime: created when the function is called, destroyed when it returns
- Stored typically on the stack
2. Global Variables
- Declared outside all functions, usually at the top of the file
- Scope extends across the entire program (all functions can access/modify it)
- Lifetime: exists for the entire duration of the program's execution
- Stored typically in a fixed data segment, not the stack
3. Shared Variables
- Variables accessible by multiple parts of a program running concurrently (e.g. multiple threads/processes)
- Common in multi-threaded/parallel programming — require synchronization (locks, mutexes) to avoid race conditions
- Not a standard single-language keyword category everywhere, but a conceptual term for variables intentionally exposed across execution units
4. Storage Classes (C/C++)
| Storage Class | Scope | Lifetime | Default Value |
|---|---|---|---|
| auto | Local (block) | Function call duration | Garbage |
| static | Local or global | Entire program run | Zero |
| extern | Global (declared elsewhere) | Entire program run | Zero |
| register | Local (block) | Function call duration | Garbage |
- A static local variable is declared inside a function but keeps its value between calls (unlike a plain local/
autovariable, which resets each call) — a classic exam trap - extern declares a variable that is defined in another file/translation unit, allowing it to be shared across multiple source files
- register is a hint to the compiler to store the variable in a CPU register for faster access (modern compilers mostly ignore this and optimize automatically)
5. Comparison Table
| Local | Global | Shared | |
|---|---|---|---|
| Scope | One function/block | Whole program | Multiple threads/processes |
| Lifetime | Function call duration | Program duration | Depends on synchronization |
| Risk | Low (isolated) | Naming conflicts, hard to trace bugs | Race conditions if unsynchronized |
6. 💻 Code Examples: C & C++
One function called twice, showing local/auto vs static vs global side by side — watch which values reset and which persist:
C:
#include <stdio.h>
int globalCount = 0; // global: whole file, whole-program lifetime
void counter(void) {
int localCount = 0; // local/auto: re-created (reset to 0) every call
static int staticCount = 0; // static local: keeps its value between calls
localCount++;
staticCount++;
globalCount++;
printf("local=%d static=%d global=%d\n", localCount, staticCount, globalCount);
}
int main(void) {
counter(); // local=1 static=1 global=1
counter(); // local=1 static=2 global=2 <- local resets, static & global persist
return 0;
}
C++:
#include <iostream>
using namespace std;
int globalCount = 0;
void counter() {
int localCount = 0;
static int staticCount = 0;
localCount++; staticCount++; globalCount++;
cout << "local=" << localCount << " static=" << staticCount
<< " global=" << globalCount << endl;
}
int main() {
counter(); // local=1 static=1 global=1
counter(); // local=1 static=2 global=2
return 0;
}
localCountprints1both times (destroyed and recreated each call);staticCountandglobalCountclimb to2(they persist for the program's lifetime) — exactly the exam trap noted below
7. MCQ Traps
- A local variable with the same name as a global variable — the local one takes precedence inside its scope (local shadows global)
- Global variables increase coupling and make debugging harder — not "always better" despite being convenient
- Shared variables in multithreaded code need synchronization to avoid race conditions — a classic exam trap topic
8. ✅ Self-Test Questions (with Answers)
Q1. What happens if a local variable has the same name as a global variable?
Answer — key points
- The local variable shadows (hides) the global one.
- Inside the block where the local is declared, every unqualified use of that name refers to the local
- the global still exists and keeps its own value, but it becomes inaccessible by its plain name within that region.
- Once the block ends, the name reverts to meaning the global again.
- This follows the compiler's innermost-scope-first name-resolution rule: it searches the current block, then the enclosing blocks, then file scope, and stops at the first match.
- In C++ the hidden global can still be reached explicitly through the scope-resolution operator
:: - in C there is no such escape — you must rename one of them.
#include <iostream>
using namespace std;
int x = 100; // GLOBAL
void demo() {
int x = 50; // LOCAL -- shadows the global inside demo()
cout << x << endl; // 50 -> the local
cout << ::x << endl; // 100 -> the global, via scope resolution (C++ only)
x = 75; // modifies the LOCAL only
::x = 200; // modifies the GLOBAL
}
int main() {
demo();
cout << x << endl; // 200 -> global was changed only by ::x
return 0;
}
// Output: 50 / 100 / 200
Shadowing also happens between nested blocks:
int y = 1;
{ int y = 2; cout << y; } // 2 -- inner block wins
cout << y; // 1 -- outer y was never touched
- MCQ trap: the global is hidden, not overwritten or destroyed — assuming the assignment inside the function changed the global is the standard wrong answer.
- This is also a real source of bugs: a programmer intending to update a global accidentally redeclares it locally, and the update silently disappears when the function returns.
- Compiling with
-Wshadowmakes the compiler warn about exactly this.
Q2. Why are global variables generally discouraged in large programs?
Answer — key points
- Because they undermine the modularity that large programs depend on.
- A global can be read or modified by any function in the program, so when its value turns out to be wrong there is no small region of code to inspect — the suspect list is the whole file.
- This produces tight coupling between functions that ought to be independent, and destroys the property that makes functions easy to reason about: that their behaviour depends only on their parameters.
- The main objections are:
- High coupling — functions become dependent on hidden shared state instead of communicating through parameters and return values
- Difficult debugging — any function could be the one that corrupted the value; tracing requires reading everything
- Namespace pollution and shadowing — the name is reserved program-wide, inviting clashes, and a same-named local silently shadows it
- Poor reusability and testability — a function relying on globals cannot be lifted into another project, nor tested in isolation, since it needs that surrounding state
- Unsafe concurrency — globals are shared by all threads, so they are prime sites for race conditions
- Permanent memory use — they occupy the data segment for the whole program run, even when needed briefly
// POOR: hidden dependency on global state
int total;
void addTax() { total *= 1.17; } // what is total? who set it? who else changes it?
// BETTER: explicit parameters, no hidden state -- testable and reusable
double addTax(double amount) { return amount * 1.17; }
cout << addTax(1000); // 1170
Legitimate uses do exist: program-wide constants (const double PI = 3.14159;, which are read-only and therefore safe), configuration values, and the standard streams cin/cout themselves. Preferred alternatives are parameters and return values, static locals when only persistence is needed, and grouping related state into a struct/class passed where required.
- MCQ trap: globals are not "always better because they save passing parameters" — convenience is precisely the trap.
- Note the shadowing rule too: inside a block, a local of the same name hides the global, and the global is then reachable only through the scope-resolution operator
::countin C++.
Q3. What problem can arise when multiple threads access a shared variable without synchronization?
Answer
- Race condition: correctness depends on uncontrolled timing of operations.
- Lost-update illustration: two operations read the same count and both store the same incremented value.
- C++ data race: conflicting unsynchronised accesses to a shared non-atomic object, with at least one write, can cause undefined behaviour.
- Fix: use a suitable mutex/atomic design; do not assume
count++is an atomic operation. - Exam example: explain the read–modify–write model, while noting that actual machine instruction sequences vary.
int count = 0; // SHARED between threads
// Thread A: count++ Thread B: count++
// Expected final value: 2. Possible interleaving:
// A loads count -> 0
// B loads count -> 0 <-- B reads BEFORE A stores
// A computes 1, stores -> count = 1
// B computes 1, stores -> count = 1 *** one increment LOST ***
#include <mutex>
std::mutex m;
int count = 0;
void safeIncrement() {
m.lock(); // enter the CRITICAL SECTION -- only one thread at a time
count++;
m.unlock(); // release
}
// Or, exception-safe: { std::lock_guard<std::mutex> g(m); count++; }
Related hazards and their remedies:
- Race condition / lost update — fixed by a mutex, lock or semaphore guarding the critical section, or by an atomic type (
std::atomic<int>) - Deadlock — two threads each hold a lock the other needs and both wait forever; avoided by always acquiring locks in the same global order
- Starvation — a thread never gets the lock because others keep taking it; addressed by fair scheduling
- Critical section — the region of code touching the shared variable; mutual exclusion means at most one thread may be inside it
- MCQ trap:
count++is not atomic — assuming it is, is the classic wrong answer. - Note also that marking a variable
volatiledoes not make it thread-safe:volatileonly prevents compiler caching of the value and gives no mutual exclusion. - Read-only shared data needs no lock; only concurrent writes (or a write with concurrent reads) create a race.
Q4. How does a static local variable differ from an ordinary (auto) local variable in terms of lifetime?
Answer
- Common property: both names have block scope.
- Ordinary local: automatic storage; a new instance is normally created for each entry and destroyed on exit.
- Static local: storage persists and the value can survive across calls.
- Initialization: zero-initialization/static initialization may occur early; required dynamic initialization happens when control first reaches the declaration.
- Modern C++:
autousually means type deduction; it is not a synonym for a special local lifetime. - Example: ordinary local counter prints 1 on each call; static counter can print 1, then 2.
#include <iostream>
using namespace std;
void counter() {
int autoVar = 0; // recreated and reset to 0 on EVERY call
static int statVar = 0; // initialised ONCE; survives between calls
autoVar++;
statVar++;
cout << autoVar << " " << statVar << endl;
}
int main() {
counter(); // 1 1
counter(); // 1 2
counter(); // 1 3
return 0;
}
| Aspect | ordinary automatic local | static local |
|---|---|---|
| Scope | Its block only | Its block only (SAME) |
| Lifetime | Until the function returns | Entire program run |
| Memory area | Stack | Data segment |
| Default value | Garbage | Zero |
| Initialiser runs | On every call | Only on the first call |
| Value between calls | Lost | Retained |
- MCQ trap: a
staticlocal is not a global — its scope is still the block, so no other function can even name it; only its lifetime is global. - This is exactly why it is safer than a global for counting calls: it gives persistence without exposing the variable to the rest of the program.
- Typical uses are call counters, caching an expensive first-time computation, and safely
returning a reference from a function.
Q5. What is the purpose of the extern keyword?
Answer
extern int count;declares a variable without defining its storage in this example.- A matching definition such as
int count = 0;supplies the object. - The definition can be in the same translation unit or another one; it need not be “somewhere else”.
- Declaration, definition and linkage must agree across the program.
externwith an initializer can be a definition, so inspect the complete declaration.- Exam example: put a declaration in a header and one definition in a source file.
// ===== file1.cpp =====
int count = 10; // DEFINITION: memory is allocated here (once)
void show();
int main() { count++; show(); } // prints 11
// ===== file2.cpp =====
#include <iostream>
extern int count; // DECLARATION only: no memory, refers to file1's count
void show() { std::cout << count; } // 11 -- the SAME variable
// g++ file1.cpp file2.cpp -o app
| Aspect | extern int count; | int count = 10; |
|---|---|---|
| Role | Declaration | Definition |
| Allocates memory? | No | Yes |
| How many times allowed | Many, in many files | Exactly once in the program |
| Resolved by | The linker | The compiler |
- MCQ trap:
externandstaticare opposites at file level. externexports a global so other files can use it;staticat file scope restricts a global to its own file, hiding it from the linker (internal linkage) — which is how you avoid name clashes between files.- Defining the variable twice (omitting
externin the second file) causes a "multiple definition" linker error, while declaring it but never defining it causes "undefined reference".
9. 📜 Past-Paper Questions & MCQs (CSS 2016–2026)
2017 · Paper-I · Q8(a) · 5 marks
Recall cue: Separate visibility from lifetime; explain shadowing; distinguish shared state from ordinary local variables.
Q. Write a short note on global, local and shared variables for nested function access.
Answer — key points
- Global variable: declared outside all functions, visible to every function in the file, and lives for the whole program. Too many globals make code hard to debug.
- Local variable: declared inside a function or block, visible only there, and created and destroyed on each call. A
staticlocal keeps its value between calls. - Shared variable: a global used by several functions or files (declared with
externin other files). Concurrent access needs protection (locks). - Nested functions: C and C++ do not allow a function inside a function (only as a compiler extension). Languages such as Pascal and Python allow an inner function to use the variables of the enclosing function, resolved by lexical (static) scope through a chain of scopes.
- Rule: the innermost declaration hides an outer variable of the same name.
Recall check and coverage
- Explain the three learning outcomes at the top without looking.
- Reproduce one example or trace and test a boundary case.
- Answer each self-test, then compare with the key.
- Attempt each recorded past-paper task on this page.
Recorded written references: 2017 · Paper-I · Q8(a) · 5 marks
Evidence limit: this page preserves the question records found in your Notion notes. Disputed labels and questions not checked against original PDFs remain flagged in the Question Bank; this is not a claim of exhaustive original-paper verification.