Menu

Scope in C: Block, Function, and File Scope Explained

Where a C variable is visible and how long it lives - block scope, function parameters, file-scope globals, static local variables that survive between calls, static functions, shadowing, and why globals cause trouble.

This page includes runnable editors - edit, run, and see output instantly.

Two Questions About Every Variable

Every variable in C has two separate properties, and confusing them is the source of most of the surprises on this page:

  • Scope - where the name can be used. A compile-time question.
  • Lifetime - how long the storage exists. A runtime question.

They usually move together (an ordinary local is visible in its block and lives exactly as long), but static decouples them, which is why it is the keyword worth understanding here.

Block Scope

A variable declared inside braces is visible from its declaration to the closing brace, and nowhere else.

The inner block can see outer, because it is nested within it. The outer block cannot see inner. This applies to every pair of braces - function bodies, if bodies, loop bodies, or a bare block like the one above.

Loops make it concrete. A counter declared in a for header belongs to the loop:

for (int i = 0; i < 5; i++) {
    /* i lives here */
}
/* i does not exist here */

Which is exactly what you want: two loops in the same function can each use i with no interference, and the counter cannot be read accidentally after the loop. If you need the value afterwards - the index a search stopped at - declare it before the loop.

Function parameters have the scope of the function body, so they behave like locals declared at the top of it.

C99 allows a declaration anywhere in a block, not only at the top. Declare variables where you first need them; a variable with a short scope is a variable with fewer ways to be wrong.

Lifetime: Automatic Storage

An ordinary local has automatic storage: it comes into existence when control enters its block and is destroyed when control leaves. Its memory lives on the stack.

It prints 1 three times. Each call gets a brand-new count, initialised to 0, and discarded on return. This is also why each frame of a recursive function has its own copy of every local.

Two consequences worth stating plainly. An uninitialised local contains garbage, not zero - whatever bytes were on the stack. And returning a pointer to a local is undefined behavior, because the storage is gone the moment the function returns:

/* BUG: buf does not exist after the return */
char *broken(void) {
    char buf[64] = "hello";
    return buf;
}

static Locals: Memory Between Calls

Put static on a local and its lifetime becomes the whole program, while its scope stays exactly the same.

Now the counter prints 1, 2, 3. The = 0 runs once, before main starts - not on each call.

Three rules for static locals:

  • The initialiser must be a constant expression, because it is applied at program start, not at runtime.
  • Without an initialiser, a static is zero-initialised (unlike an automatic local, which holds garbage).
  • The variable is still private to the function. No other code can name it.

That last point is the whole appeal: a function can remember something between calls without exposing a global that anything could modify. Use it for call counters, cached lookup tables built on first use, and one-time initialisation flags.

The cost is that the function is no longer a pure input-to-output mapping - the same arguments can give different answers - which makes it harder to test and unsafe to call from several threads at once without protection. Use static locals deliberately, not as a convenience.

File Scope: Globals

A variable declared outside every function has file scope. It is visible from its declaration to the end of the file, in every function below it, and its lifetime is the entire program.

Globals are zero-initialised by default, so totalOperations starts at 0 even without the = 0.

By default a global also has external linkage: other .c files in the same program can reach it by declaring it extern:

/* in stats.c */
int totalOperations = 0;          /* the definition - exactly one in the program */

/* in main.c */
extern int totalOperations;       /* a declaration: "it exists somewhere" */

Put the extern declaration in a header file so every user sees the same one. Note the asymmetry: the extern declaration says the variable exists, and exactly one .c file must actually define it.

Why Globals Bite

Globals are the easiest way to share data and the easiest way to create bugs you cannot localise. Four concrete problems:

Anything can change them. When totalOperations holds a wrong value, the culprit is any line in any file. With a parameter, the suspects are the call sites you can see.

Functions become untestable. A function that reads a global cannot be called in isolation; you have to set up global state first, and remember to reset it afterwards.

The name is program-wide. A global called count or buffer will eventually collide with someone else's.

Their initializers must be compile-time constants. In C, a global can only be initialised with a constant expression - int limit = readConfig(); will not compile, and int b = a * 2; at file scope will not either. Anything computed has to be assigned at run time by some setup function, and every file that touches the global before that call runs sees a silent 0.

The alternatives are almost always available: pass values as parameters, return results, and bundle related state into a struct that gets passed around explicitly. Genuine exceptions exist - a program-wide configuration object, a logging handle - and even those are better as static at file scope with accessor functions, which is the next section.

static at File Scope: Privacy

On a global variable or a function, static means something completely different from what it means on a local: internal linkage. The name becomes private to its own .c file and cannot be reached from any other.

/* counter.c */
#include "counter.h"

static int count = 0;           /* private to this file - no other file can touch it */

static void validate(void) {    /* a private helper, not part of the interface */
    if (count < 0) count = 0;
}

void increment(void) {          /* public: declared in counter.h */
    count++;
    validate();
}

int getCount(void) {            /* public */
    return count;
}

Another file can call increment and getCount, but it cannot see count and cannot call validate - the linker will not resolve those names. That is C's module system, such as it is: a header declaring the public functions, and static on everything else.

Two practical benefits beyond tidiness. Two files can each have a static void validate(void) with no collision, which they could not if the functions were public. And the compiler knows a static function has no callers outside the file, so it can inline it more aggressively or warn that it is unused.

So the keyword has two distinct meanings, decided by where it appears:

static on a LOCAL variable   ->  lifetime becomes permanent (scope unchanged)
static at FILE scope         ->  linkage becomes internal    (lifetime unchanged)

Shadowing

Declaring a name in an inner scope that already exists in an outer one shadows the outer name: within the inner scope, the name refers to the new variable and the outer one is unreachable.

This is legal, and occasionally intentional. More often it is an accident, and a costly one: a function meant to update a global instead updates a local with the same name, and the global never changes. The same happens when a local shadows a parameter, so the assignment you meant for the input goes nowhere.

gcc -Wshadow reports every case. It is not in -Wall, so turn it on explicitly:

gcc -Wall -Wextra -Wshadow program.c -o program

The habit that avoids the problem entirely is naming by role rather than by type: totalScore and itemScore cannot shadow each other, while two variables both called n eventually will.

A Quick Reference

declared inside a block        block scope, automatic lifetime, garbage if uninitialised
function parameter             block scope of the function body, a copy of the argument
static inside a function       block scope, PROGRAM lifetime, zero-initialised, kept between calls
declared outside all functions file scope, program lifetime, zero-initialised, visible to other files
static outside all functions   file scope, program lifetime, PRIVATE to this .c file
extern declaration             names a variable defined in another file

Frequently Asked Questions

What is scope in C?

The region of code where a name is visible. A variable declared inside a block (a pair of braces) is visible only in that block - that is block scope. One declared outside every function has file scope and is visible from its declaration to the end of the file.

What does static do to a local variable in C?

It changes the variable's lifetime without changing its scope. A static local is created once, initialised once, and keeps its value between calls, but it is still visible only inside its function. It is the way to give a function memory without using a global.

What is the difference between static and global in C?

A plain global is visible to every file in the program, which other files can reach with extern. A static variable or function at file scope is private to that one .c file - other files cannot link to it. static at file scope is about hiding; static on a local is about lifetime.

Why are global variables bad in C?

Any function can change them, so a bug can be caused from anywhere in the program; functions that read them cannot be tested in isolation; and the name occupies the whole program, inviting collisions. Pass values as parameters and return results instead, and where shared state is genuinely needed, make it static at file scope so only one file can touch it.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED