CSS Prepare

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

QuestionExample
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++ auto normally means type deduction; automatic storage duration is a separate concept.
  • A global is not automatically visible everywhere: declarations, scope and linkage matter.
  • extern does 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 ClassScopeLifetimeDefault Value
autoLocal (block)Function call durationGarbage
staticLocal or globalEntire program runZero
externGlobal (declared elsewhere)Entire program runZero
registerLocal (block)Function call durationGarbage
  • A static local variable is declared inside a function but keeps its value between calls (unlike a plain local/auto variable, 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

LocalGlobalShared
ScopeOne function/blockWhole programMultiple threads/processes
LifetimeFunction call durationProgram durationDepends on synchronization
RiskLow (isolated)Naming conflicts, hard to trace bugsRace 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;
}
  • localCount prints 1 both times (destroyed and recreated each call); staticCount and globalCount climb to 2 (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 -Wshadow makes 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 ::count in 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 volatile does not make it thread-safe: volatile only 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++: auto usually 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;
}
Aspectordinary automatic localstatic local
ScopeIts block onlyIts block only (SAME)
LifetimeUntil the function returnsEntire program run
Memory areaStackData segment
Default valueGarbageZero
Initialiser runsOn every callOnly on the first call
Value between callsLostRetained
  • MCQ trap: a static local 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.
  • extern with 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
Aspectextern int count;int count = 10;
RoleDeclarationDefinition
Allocates memory?NoYes
How many times allowedMany, in many filesExactly once in the program
Resolved byThe linkerThe compiler
  • MCQ trap: extern and static are opposites at file level.
  • extern exports a global so other files can use it; static at 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 extern in 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 static local keeps its value between calls.
  • Shared variable: a global used by several functions or files (declared with extern in 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.