Introduction

Every identifier in a C program raises three separate questions:

  1. Scope: in which part of the source text can this name be used?
  2. Storage duration (lifetime): when does the object exist at run time?
  3. 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

Core concepts

Scope

C11 defines four kinds of scope.

ScopeWhere it starts and endsExample
Blockfrom the declaration to the end of the enclosing { }; also parameters of a function definition, and variables declared in a for headerint i inside a loop body
Filefrom the declaration to the end of the translation unita global variable or function
Functionthe whole function bodylabels used by goto, and only labels
Function prototypethe parameter list of a prototypen 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

DurationCreatedDestroyedInitial value if not initialisedObjects
Staticbefore main startswhen the program endszero (arithmetic 0, pointers null)file-scope objects and block-scope static/extern objects unless declared _Thread_local; string literals
Automaticnon-VLA: on entry to the associated block; VLA: when its declaration is reachednon-VLA: when execution of that block ends; VLA: when execution leaves its declaration's scopeindeterminateblock-scope objects without static, extern or _Thread_local; parameters
Allocatedby malloc, calloc, reallocby freeindeterminate (calloc: all bits zero)see Dynamic Memory Allocation
Threadwhen the thread startswhen the thread endszeroobjects 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 static at file scope.
  • No linkage: each declaration is a separate entity. Block-scope variables without extern, parameters, typedef names, and structure tags have no linkage.

The storage-class specifiers

SpecifierAt block scopeAt file scope
noneautomatic duration unless _Thread_local, no linkagestatic duration unless _Thread_local, external linkage
autosame as none (the default; rarely written)not allowed
registerautomatic; a hint to keep it in a register; its address may not be takennot allowed
staticstatic duration, no linkage: keeps its value between callsstatic duration, internal linkage: private to the file
externrefers to an object defined elsewhere (usually at file scope), with its linkagedeclares without defining (unless an initialiser is given)
_Thread_localmust be combined with static or extern; one instance per threadone 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 that count is 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 of count, 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:

SegmentContentsStorage duration
textmachine code of functions(not objects)
read-only datastring literals and many const staticsstatic
datastatic-duration objects with a nonzero initialiserstatic
BSSstatic-duration objects that are zero-initialised or have no initialiserstatic
heapblocks from malloc and friends; grows upwardallocated
stackframes with automatic variables and parameters; grows downwardautomatic

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).

Memory segments of a C process
Figure 1. A C process image from low to high addresses: text, read-only data, data, BSS, heap growing up, free space, stack growing down; each segment labelled with the kind of object it holds and an example declaration.

Worked examples

All outputs below are for C11 with gcc on x86-64 Linux (LP64).

Example 1 (scope and shadowing).

C
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).

C
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).

C
/* 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.

C
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.
  • static inside a function changes duration (to static); static outside 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.
  • register variables cannot have their address taken (gcc reports an error).
  • Labels have function scope and goto cannot 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 extern declaration 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 static do to a global?" restricts it to its file.

Summary

Sources

  1. 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).
  2. 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).

Next steps