Introduction
Every identifier in a C program raises three separate questions:
- Scope: in which part of the source text can this name be used?
- Storage duration (lifetime): when does the object exist at run time?
- Linkage: do two declarations of the same name, perhaps in different source files, refer to the same object or function?
The storage-class specifiers auto, register, static, extern and _Thread_local adjust these properties. Mixing up the three questions is the usual cause of errors, especially with static, which means different things inside and outside a function.
Conventions: C11, hosted, gcc on x86-64 Linux as the reference platform.
Prerequisites
- Functions and Parameter Passing: calls, stack frames,
staticlocal variables.
Core concepts
Scope
C11 defines four kinds of scope.
| Scope | Where it starts and ends | Example |
|---|---|---|
| Block | from the declaration to the end of the enclosing { }; also parameters of a function definition, and variables declared in a for header | int i inside a loop body |
| File | from the declaration to the end of the translation unit | a global variable or function |
| Function | the whole function body | labels used by goto, and only labels |
| Function prototype | the parameter list of a prototype | n in int f(int n); |
Shadowing. A declaration in an inner block hides any declaration of the same name in an outer scope, until the inner block ends. C uses static (lexical) scoping: which declaration a name refers to is decided from the program text, not from the call history.
Storage duration
| Duration | Created | Destroyed | Initial value if not initialised | Objects |
|---|---|---|---|---|
| Static | before main starts | when the program ends | zero (arithmetic 0, pointers null) | file-scope objects and block-scope static/extern objects unless declared _Thread_local; string literals |
| Automatic | non-VLA: on entry to the associated block; VLA: when its declaration is reached | non-VLA: when execution of that block ends; VLA: when execution leaves its declaration's scope | indeterminate | block-scope objects without static, extern or _Thread_local; parameters |
| Allocated | by malloc, calloc, realloc | by free | indeterminate (calloc: all bits zero) | see Dynamic Memory Allocation |
| Thread | when the thread starts | when the thread ends | zero | objects declared _Thread_local |
A static-duration object is initialised once, and its initialiser must be a constant expression. An automatic object with an initialiser is re-initialised every time its declaration is reached.
Linkage
- External linkage: every declaration with external linkage in the whole program denotes the same object or function. File-scope objects and functions have external linkage by default.
- Internal linkage: the name refers to the same entity throughout one translation unit only. Produced by
staticat file scope. - No linkage: each declaration is a separate entity. Block-scope variables without
extern, parameters,typedefnames, and structure tags have no linkage.
The storage-class specifiers
| Specifier | At block scope | At file scope |
|---|---|---|
| none | automatic duration unless _Thread_local, no linkage | static duration unless _Thread_local, external linkage |
auto | same as none (the default; rarely written) | not allowed |
register | automatic; a hint to keep it in a register; its address may not be taken | not allowed |
static | static duration, no linkage: keeps its value between calls | static duration, internal linkage: private to the file |
extern | refers to an object defined elsewhere (usually at file scope), with its linkage | declares without defining (unless an initialiser is given) |
_Thread_local | must be combined with static or extern; one instance per thread | one instance per thread |
typedef is grammatically a storage-class specifier, but it only creates a type alias. At most one storage-class specifier may appear in a declaration (with _Thread_local allowed alongside static or extern).
Declarations and definitions across files
int count = 5;at file scope is a definition with external linkage.extern int count;is a declaration only: it says thatcountis defined somewhere, possibly in another translation unit.int count;at file scope with no initialiser is a tentative definition. If the unit contains no other definition ofcount, it becomes a definition initialised to 0.- A function declaration is implicitly
extern.static int helper(void)makes the function private to its file. - An object with external linkage must be defined exactly once in the whole program (when it is used); otherwise the linker reports an undefined reference or a multiple definition.
How it works: where objects live
A typical Linux process image places objects of different storage durations in different segments:
| Segment | Contents | Storage duration |
|---|---|---|
| text | machine code of functions | (not objects) |
| read-only data | string literals and many const statics | static |
| data | static-duration objects with a nonzero initialiser | static |
| BSS | static-duration objects that are zero-initialised or have no initialiser | static |
| heap | blocks from malloc and friends; grows upward | allocated |
| stack | frames with automatic variables and parameters; grows downward | automatic |
The standard does not mention segments; this is the conventional layout used by Unix-like systems. Running nm on an object file shows an initialised global in data (D), an uninitialised global in BSS (B) and a function in text (T).

Worked examples
All outputs below are for C11 with gcc on x86-64 Linux (LP64).
Example 1 (scope and shadowing).
int x = 1;
void show(void) { printf("%d ", x); }
int main(void) {
printf("%d ", x); /* 1: the global */
int x = 2;
printf("%d ", x); /* 2: the local hides the global */
{ int x = 3; printf("%d ", x); show(); } /* 3, then show prints 1 */
printf("%d ", x); /* 2 */
for (int x = 9; x < 10; x++) printf("%d ", x); /* 9 */
printf("%d\n", x); /* 2 */
}Output: 1 2 3 1 2 9 2. show prints the global because C resolves x inside show from the text of show, where only the global is visible.
Example 2 (static versus automatic local).
int f(void) { static int s = 0; int a = 0; s += 2; a += 2; return s * 10 + a; }Three calls return 22, 42, 62: s keeps its value (2, 4, 6) while a is re-created as 0 each time.
Example 3 (default initialisation). File-scope int g; static int h; int arr[3]; char *p; and block-scope static double d; are all zero: g, h and arr[2] are 0, p is a null pointer and d is 0.0. An automatic int y; has an indeterminate value; reading it is undefined behaviour if its address is never taken.
Example 4 (linkage across two files).
/* a.c */
int count = 5;
static int hidden = 7;
int get_hidden(void) { return hidden; }
void bump(void) { count++; }
/* b.c */
extern int count;
int get_hidden(void);
void bump(void);
static int hidden = 100; /* a different object from a.c's hidden */
int main(void) { bump(); bump(); printf("%d %d %d\n", count, get_hidden(), hidden); }Output: 7 7 100. Both files share count (external linkage). The two hidden objects are separate (internal linkage). A third file that writes extern int hidden; and uses it fails to link with "undefined reference to hidden'", because a.c's hidden is invisible outside a.c`.
Example 5 (static versus dynamic scoping). C is statically scoped. Under dynamic scoping, a free name refers to the most recent active binding in the call chain.
int x = 10;
int f(void) { return x; }
int g(void) { int x = 20; return f(); }Under static scoping (C), g() returns 10: f sees the global x. Under dynamic scoping, g() returns 20: when f runs, the most recent binding of x on the call stack is g's local. Details: Parameter Passing and Static vs Dynamic Scope.
Important properties and observations
- Scope is a compile-time property of the text; storage duration is a run-time property of the object; linkage connects declarations.
staticinside a function changes duration (to static);staticoutside a function changes linkage (to internal). Neither changes scope.- Static-duration objects are zero-initialised by default; automatic objects are not.
- A static-duration initialiser must be a constant expression;
static int s = f();does not compile. registervariables cannot have their address taken (gcc reports an error).- Labels have function scope and
gotocannot cross functions. Within one function, it shall not jump from outside into the scope of an identifier with a variably modified type; ordinary block crossings still require care about initialization and lifetimes. - An
externdeclaration without an initializer normally declares rather than defines an object. At file scope,extern int x = 1;is a definition and allocates storage; preserve any linkage inherited from a visible prior declaration.
GATE examination perspective
- Output with shadowing: find, for each use of a name, the innermost visible declaration in the text.
- Static locals: the value persists; count the calls.
- Static versus dynamic scoping: trace the call chain for dynamic scope, the program text for static scope.
- Default values: globals and statics print 0; uninitialised locals are indeterminate (a correct answer may be "garbage" or "undefined").
- Linkage: "what does
staticdo to a global?" restricts it to its file.
Summary
Sources
- ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Section 6.2.1 (scopes of identifiers), 6.2.2 (linkages of identifiers), 6.2.4 (storage durations of objects), 6.7.1 (storage-class specifiers), 6.7.9 (initialization, paragraph 10 on default zero initialisation), 6.9.2 (external object definitions, tentative definitions).
- B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Sections 4.3 to 4.9 (external variables, scope rules, header files, static variables, register variables, block structure, initialization).