Nearby lessons

81 of 124

C - Scope

Scope is the region of a program where a name is visible. Learn C's four scopes — block, function, file and global — plus variable shadowing, lifetime, and why local variables should be your default.

Block Scope

A variable declared inside braces exists only inside those braces — including the body of an if or a for:

Example01
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int outer = 10;
6 
7 { /* a nested block */
8 int inner = 20;
9 printf("Inside : outer=%d inner=%d\n", outer, inner);
10 } /* inner dies here */
11 
12 /* printf("%d", inner); ERROR: 'inner' undeclared */
13 printf("Outside: outer=%d\n", outer);
14 return 0;
15}
Output
Inside : outer=10 inner=20
Outside: outer=10

Loop Variables Are Block-Scoped

Declaring the counter in the for header keeps it out of the surrounding code — good practice in C99 and later:

Example02
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 for (int i = 0; i < 3; i++) /* i lives only in the loop */
6 printf("i = %d\n", i);
7 
8 /* printf("%d", i); ERROR: 'i' undeclared here */
9 
10 for (int i = 10; i < 12; i++) /* a brand new i - no conflict */
11 printf("i = %d\n", i);
12 return 0;
13}
Output
i = 0
i = 1
i = 2
i = 10
i = 11

Function Scope

Each function has its own private set of names. Two functions may both use count with no interference:

Example03
CCode Cell
1#include <stdio.h>
2 
3void functionA(void)
4{
5 int count = 100;
6 printf("In A: count = %d\n", count);
7}
8 
9void functionB(void)
10{
11 int count = 999; /* completely unrelated to A's count */
12 printf("In B: count = %d\n", count);
13}
14 
15int main()
16{
17 functionA();
18 functionB();
19 return 0;
20}
Output
In A: count = 100
In B: count = 999

Global Scope

A variable declared outside every function is visible to all of them — and modifiable by all of them:

Example04
CCode Cell
1#include <stdio.h>
2 
3int globalCounter = 0; /* visible everywhere below */
4 
5void increment(void) { globalCounter++; }
6void show(void) { printf("globalCounter = %d\n", globalCounter); }
7 
8int main()
9{
10 show();
11 increment();
12 increment();
13 show();
14 return 0;
15}
Output
globalCounter = 0
globalCounter = 2

Shadowing — Inner Wins

When an inner declaration reuses an outer name, the inner one hides the outer one for the rest of that block:

Example05
CCode Cell
1#include <stdio.h>
2 
3int value = 100; /* global */
4 
5int main()
6{
7 printf("Global : %d\n", value);
8 
9 int value = 200; /* shadows the global */
10 printf("Local : %d\n", value);
11 
12 {
13 int value = 300; /* shadows the local */
14 printf("Inner block : %d\n", value);
15 }
16 
17 printf("Back to local: %d\n", value);
18 return 0;
19}
Output
Global      : 100
Local       : 200
Inner block : 300
Back to local: 200

Scope vs Lifetime

In simple words: scope is where a name can be seen; lifetime is how long the storage exists. A static local has narrow scope but a long lifetime: only its function can name it, yet its value survives between calls.
Example06
CCode Cell
1#include <stdio.h>
2 
3void normalCounter(void)
4{
5 int count = 0; /* new storage every call */
6 count++;
7 printf("normal: %d\n", count);
8}
9 
10void staticCounter(void)
11{
12 static int count = 0; /* storage persists across calls */
13 count++;
14 printf("static: %d\n", count);
15}
16 
17int main()
18{
19 normalCounter(); normalCounter(); normalCounter();
20 staticCounter(); staticCounter(); staticCounter();
21 return 0;
22}
Output
normal: 1
normal: 1
normal: 1
static: 1
static: 2
static: 3

File Scope with static

static on a global restricts it to its own .c file — a module-private variable:

Example07
CCode Cell
1/* ---------- config.c ---------- */
2static int secretKey = 12345; /* file scope - private */
3int publicSetting = 100; /* global - shared */
4 
5int getKey(void) { return secretKey; }
6 
7/* ---------- main.c ---------- */
8#include <stdio.h>
9 
10extern int publicSetting; /* accessible */
11/* extern int secretKey; link error - it is static */
12int getKey(void);
13 
14int main()
15{
16 printf("publicSetting = %d\n", publicSetting);
17 printf("key via getter = %d\n", getKey());
18 return 0;
19}
Output
publicSetting = 100
key via getter = 12345

The Four Scopes

ScopeDeclaredVisibleLifetime
BlockInside bracesTo the closing braceUntil the block exits
FunctionIn the bodyThroughout the functionUntil the function returns
File (static)Outside functions, with staticThat .c file onlyThe whole program
GlobalOutside functionsAll files (with extern)The whole program

Parameters Have Function Scope

Parameters behave like locals declared at the top of the body — visible throughout the function, invisible outside it:

Example09
CCode Cell
1#include <stdio.h>
2 
3int process(int input) /* input is scoped to this function */
4{
5 int doubled = input * 2;
6 
7 if (doubled > 10)
8 {
9 int bonus = 5; /* only inside the if */
10 doubled += bonus;
11 }
12 /* bonus is gone here */
13 
14 return doubled;
15}
16 
17int main()
18{
19 printf("process(3) = %d\n", process(3));
20 printf("process(10) = %d\n", process(10));
21 return 0;
22}
Output
process(3)  = 6
process(10) = 25

Why Globals Cause Trouble

Any function can change a global, so tracking down a wrong value means reading the whole program:

Example10
CCode Cell
1#include <stdio.h>
2 
3int total = 0; /* who changes this? Everyone. */
4 
5void addTen(void) { total += 10; }
6void reset(void) { total = 0; }
7void addFifty(void) { total += 50; }
8 
9int main()
10{
11 addTen();
12 addFifty();
13 reset(); /* easy to forget this happened */
14 addTen();
15 printf("total = %d (surprised?)\n", total);
16 return 0;
17}
Output
total = 10  (surprised?)

The Same Logic Without Globals

Pass the value in, return the result out. Now every function's behaviour is visible at the call site:

Example11
CCode Cell
1#include <stdio.h>
2 
3int addTen(int total) { return total + 10; }
4int addFifty(int total) { return total + 50; }
5 
6int main()
7{
8 int total = 0;
9 
10 total = addTen(total);
11 total = addFifty(total);
12 printf("total = %d\n", total);
13 return 0;
14}
Output
total = 60

Common Mistakes

  • Using a variable outside its block — the "undeclared identifier" error.
  • Accidental shadowing — an inner variable silently hides the one you meant to update.
  • Expecting a local to persist — it resets every call unless declared static.
  • Reaching for a global to avoid a parameter — convenient now, painful later.
  • Declaring the loop counter outside the loop — leaks the name into the surrounding scope.
  • Assuming an uninitialised local is zero — only globals and statics are zero-initialised.
Shadowing is legal, which is what makes it dangerous. If a function declares int total; while a global total also exists, every assignment inside that function updates the local and the global never moves. Build with -Wshadow to be warned.
📝 Key Takeaways
  • A variable is visible from its declaration to the end of its enclosing block.
  • Local variables are destroyed when their block exits.
  • An inner declaration shadows an outer one of the same name.
  • Globals are visible everywhere and are best avoided.
  • static gives a local variable a lifetime that spans all calls.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4