Storage Classes in C: Scope, Lifetime and Linkage

C process memory layout mapping auto, register, static, and extern variables to CPU registers, stack frames, and .data/.bss segments.
C process memory layout mapping auto, register, static, and extern variables to CPU registers, stack frames, and .data/.bss segments.

The familiar storage-class keywords auto, register, static and extern influence an object's storage duration or an identifier's linkage. They do not all name memory regions. Use three separate questions: where is the name visible, how long does the object exist, and can declarations in different scopes or translation units denote the same entity? This tutorial uses C17 semantics.

1. Scope is not lifetime or linkage

Scope describes where an identifier is visible in source code. Storage duration describes how long an object's storage exists. Linkage connects declarations of the same entity: none, internal, or external. A block-scope static counter has block scope, static storage duration and no linkage. A file-scope static object has file scope, static storage duration and internal linkage. Static duration therefore does not imply global visibility.

2. auto: the ordinary local variable

In C17, a typical local variable declared without a storage-class specifier has automatic storage duration. Its lifetime begins when execution enters its block and ends when that block is left, subject to special rules for variable-length arrays. Its value is indeterminate unless initialized. auto int n = 0 explicitly states a default that is normally omitted. Do not return its address to a caller: the object will no longer exist. Compilers often use a stack frame, but the C standard does not require a physical stack location.

void work(void) {
    auto int count = 0; /* Same ordinary duration as int count = 0. */
    ++count;
}

3. register: a hint, not a placement guarantee

register requests fast access for an automatic object. The implementation may ignore the hint; optimized ordinary locals may also reside in registers or disappear entirely. Under C17 you cannot apply the address-of operator to an object declared register. It is not a tool for mapping a variable onto a specific hardware register, and it does not mean volatile. C23 changes some language rules, so keep your compiler standard explicit when following this C17 reference.

register int index = 0;
/* int *p = &index; is invalid in C17. */

4. static inside a function

A local static object exists for the entire execution and is initialized once before program startup, not reinitialized on each function call. Without an explicit initializer it is zero-initialized. A C17 static-storage initializer must satisfy the language's constant-initialization constraints; it is not C++ dynamic local initialization. A static counter preserves values across calls but introduces shared state: simultaneous unsynchronized writes from multiple threads can be a data race.

static int next_id(void) {
    static int id;
    return ++id; /* Assumes calls stay within int range. */
}

5. static at file scope and extern across files

File-scope static gives an object or function internal linkage. An extern declaration usually refers to an object defined elsewhere and does not itself allocate a second object when there is no initializer. Put a declaration in a guarded header and exactly one definition in a C file. An extern declaration with an initializer is a definition. A prior visible declaration can determine linkage, so extern does not always force external linkage over an earlier static declaration.

/* counter.h */
#ifndef COUNTER_H
#define COUNTER_H
extern int total;
void add_one(void);
#endif

/* counter.c */
#include "counter.h"
int total = 0;
void add_one(void) { ++total; }

/* main.c */
#include <stdio.h>
#include "counter.h"
int main(void) {
    add_one();
    printf("%d\n", total);
    return 0;
}
/* Build separate files: gcc -std=c17 main.c counter.c -o app */

6. Memory diagrams and the full language vocabulary

Initialized static-duration objects commonly occupy .data and zero-initialized ones .bss, while automatic variables commonly use a stack or registers. These are implementation patterns, not requirements of the keywords. extern is a declaration/linkage mechanism, not a separate memory segment. C17 also has _Thread_local for thread storage duration and typedef in the grammar's storage-class-specifier category; typedef creates a type alias rather than object storage. Dynamically allocated memory has allocated storage duration and is not selected by auto or register.

7. Diagnose linker errors without changing lifetimes blindly

An undefined reference to a shared object often means the defining C file was not linked, a name differs, or the definition has internal linkage. Multiple definitions often mean a header contains a definition included into several files. Do not remove static indiscriminately: first decide whether the symbol belongs to one translation unit or is deliberately shared. The runnable example below needs only one source file and shows automatic reset versus static persistence.

Runnable C example

#include <stdio.h>

static int file_total;

static void visit(void) {
    auto int local = 0;
    static int persistent = 0;
    register int step = 1;
    local += step;
    persistent += step;
    file_total += step;
    printf("local=%d persistent=%d total=%d\n",
           local, persistent, file_total);
}

int main(void) {
    visit();
    visit();
    visit();
    return 0;
}

Open example in compiler

Expected Output

local=1 persistent=1 total=1
local=1 persistent=2 total=2
local=1 persistent=3 total=3

Frequently Asked Questions

What is the difference between static and extern?

File-scope static gives internal linkage. extern commonly declares an object defined in another translation unit; the object still has static storage duration. A local static keeps its value across calls without making its name visible outside the block.

Does register guarantee a CPU register?

No. It is a hint that can be ignored. In C17 it also prevents taking the declared object's address, regardless of where the compiler stores it.

Are automatic variables initialized to zero?

Not by default. Initialize them before use. Objects with static storage duration are zero-initialized when no explicit initializer is supplied.

Can I define a global variable in a header?

A header definition included in multiple translation units can cause multiple definitions. Prefer an extern declaration in the header and one definition in a C file. Header static objects instead create distinct internal-linkage objects in each translation unit.

Related learn