C scope and storage classes
last modified November 14, 2026
Three related ideas decide what a name refers to and how long an object lives.
Scope is the part of the source in which a name is visible.
Storage duration is the part of the program's execution in which the
object exists. Linkage says whether another translation unit can refer
to the same name. C expresses these properties with the storage-class specifiers
auto, static, extern and
register, and with the place where a declaration appears. This
tutorial covers block and file scope, shadowing, automatic and static lifetime,
external declarations and the register hint.
Block scope
A declaration inside a block is visible from that declaration to the closing
brace of the block. A nested block may declare another object with the same
name; the inner declaration hides the outer one until the inner block ends, and
then the outer name is visible again. The two objects are distinct even though
they share a spelling, so changing the inner one does not change the outer one.
The next program declares value three times in nested blocks.
#include <stdio.h>
int main(void) {
int value = 10;
printf("outer value: %d\n", value);
{
int value = 20;
printf("inner value: %d\n", value);
{
int value = 30;
printf("innermost value: %d\n", value);
}
printf("inner value again: %d\n", value);
}
printf("outer value again: %d\n", value);
return 0;
}
Each pair of braces introduces a new scope. The inner declarations are new objects initialised to 20 and 30, and the print after the innermost block sees the middle object again. When the outer block ends, the original object with the value 10 is visible once more. Shadowing is legal, but a long function with several objects of the same name quickly becomes hard to read.
$ gcc -std=c17 -Wall -Wextra -o scope_blocks scope_blocks.c $ ./scope_blocks outer value: 10 inner value: 20 innermost value: 30 inner value again: 20 outer value again: 10
Loop scope and shadowing
A declaration in the first clause of a for statement has the loop
as its scope. The variable exists from its declaration to the end of the loop
body and is destroyed when the loop finishes. An outer variable with the same
name is not modified by the loop; the loop declaration simply hides it. This
form became available in C99, and it keeps the counter from leaking into the
rest of the function.
#include <stdio.h>
int main(void) {
int i = 100;
printf("before loop: i = %d\n", i);
for (int i = 0; i < 3; i++) {
printf("loop i = %d\n", i);
}
printf("after loop: i = %d\n", i);
return 0;
}
The outer i is 100 before the loop and is still 100 afterwards.
Inside the loop, the declaration int i = 0 creates a different
object whose values are 0, 1 and 2. The compiler option -Wshadow
reports such hiding when it is enabled; it is not part of
-Wall -Wextra because shadowing is sometimes intentional.
$ gcc -std=c17 -Wall -Wextra -o scope_loop scope_loop.c $ ./scope_loop before loop: i = 100 loop i = 0 loop i = 1 loop i = 2 after loop: i = 100
Automatic storage duration
A local variable without static has automatic storage duration. It
comes into existence on entry to the block and is initialised when execution
reaches its declaration; it is destroyed when the block exits. auto
is the storage-class specifier that names this default, but it is almost never
written because it adds nothing. An automatic object with no initialiser has an
indeterminate value, so it must be assigned before it is read.
#include <stdio.h>
void show_auto(void) {
auto int value = 0;
value++;
printf("automatic value: %d\n", value);
}
int main(void) {
show_auto();
show_auto();
show_auto();
return 0;
}
Each call to show_auto creates a new value, sets it to
zero and increments it. The printed result is therefore 1 every time, not 1, 2,
3. The auto int spelling is equivalent to int; the
keyword remains in the language mainly for historical symmetry with
static and extern.
$ gcc -std=c17 -Wall -Wextra -o storage_auto storage_auto.c $ ./storage_auto automatic value: 1 automatic value: 1 automatic value: 1
Static local variables
Adding static to a local declaration changes its storage duration
but not its scope. The object is created once, before main starts,
and lives until the program ends; the name is still visible only inside its
block. An initialiser on a static object is evaluated only once, and an
uninitialised static object starts at zero. The next function uses a static
counter to remember how many times it has been called.
#include <stdio.h>
void tick(void) {
static int calls = 0;
calls++;
printf("call %d\n", calls);
}
int main(void) {
tick();
tick();
tick();
return 0;
}
The first call initialises calls to zero, increments it and prints
1. The second and third calls reuse the same object, so the output continues
with 2 and 3. A static local is not a global: code outside the function cannot
name it, even though the object is alive for the whole run. It is also shared by
all calls, so it is not a safe per-thread counter in a multithreaded program.
$ gcc -std=c17 -Wall -Wextra -o storage_static_local storage_static_local.c $ ./storage_static_local call 1 call 2 call 3
File scope and internal linkage
A declaration outside every function has file scope: it is visible from that
point to the end of the translation unit. Adding static to such a
declaration gives the object or function internal linkage, which means other
translation units cannot refer to it. The variable and the helper below are
therefore private to this source file. A static object at file
scope is zero-initialised before the program starts, like any other object with
static storage duration.
#include <stdio.h>
static int counter = 0;
static void bump(void) {
counter++;
}
int main(void) {
bump();
bump();
printf("counter: %d\n", counter);
return 0;
}
Both counter and bump have file scope and internal
linkage. main can use them because it is in the same
translation unit; a second .c file compiled into the same program
could not call bump or read counter. The two calls
increment the counter to 2. Without static, the names would have
external linkage and could clash with names in other files.
$ gcc -std=c17 -Wall -Wextra -o storage_static_file storage_static_file.c $ ./storage_static_file counter: 2
External declarations
The extern specifier declares an object without defining it. It
tells the compiler the name and type and leaves the storage to a definition
elsewhere, either later in the same file or in another translation unit. The
definition is the declaration that actually allocates the object, such as
int shared = 10;. A program may have many declarations of the same
external object but only one definition.
#include <stdio.h>
extern int shared;
void add(int n) {
shared += n;
}
int shared = 10;
int main(void) {
printf("shared: %d\n", shared);
add(5);
printf("shared: %d\n", shared);
return 0;
}
extern int shared; lets add use the variable before
its definition appears. The definition int shared = 10; allocates
the object and gives it the initial value. After one call to add(5)
the value is 15. The same mechanism connects files in a larger program; the
C header files tutorial puts the declaration in a
header so every source file sees it.
$ gcc -std=c17 -Wall -Wextra -o storage_extern storage_extern.c $ ./storage_extern shared: 10 shared: 15
The register specifier
register asks the compiler to keep an object in a processor
register if possible. It is only a hint: a modern optimising compiler decides
where values live and may ignore the request entirely. A register object cannot
have its address taken, so &i is a compile error. The keyword
can appear only on a block-scope declaration or on a function parameter, and it
is rarely useful in code written for current compilers.
#include <stdio.h>
int main(void) {
register int i;
int sum = 0;
for (i = 1; i <= 5; i++) {
sum += i;
}
printf("sum: %d\n", sum);
return 0;
}
The loop variable is declared once before the loop and reused by the
for statement. The hint does not change the result: the five
integers add up to 15. If the program needed a pointer to i, the
declaration would have to drop register and the compiler would be
free to place the object in memory.
$ gcc -std=c17 -Wall -Wextra -o storage_register storage_register.c $ ./storage_register sum: 15
Source
cppreference: scope
cppreference: storage duration and linkage
cppreference: storage-class specifiers
cppreference: extern
This tutorial covered scope and storage classes in C: block scope and
shadowing, the scope of a declaration in a for header, automatic
and static storage duration, file scope with internal linkage,
extern declarations and the register hint. The
C functions tutorial uses these rules for
parameters and local variables, the C arrays
tutorial shows how automatic arrays are created, and the
C header files tutorial puts external declarations
in a shared header.
Author
List all C tutorials.