Storage Classes in C: auto, register, static and extern with Examples

Learn what auto, register, static and extern actually control in C. Trace persistent and fresh objects, then connect two source files without creating duplicate state.

KnowledgeGate Team

Exam prep & CS education

Updated 7 Sep 20266 min read

auto, register, static and extern govern how long objects exist, where names are visible and whether declarations in different source files denote the same object. A local static retains its value between calls, while extern connects an external declaration to a definition. For a broader map of the language, see C Programming & Data Structures.

Storage Duration, Scope and Linkage Are Different Questions

For a variable named total, storage duration asks how long the object exists. Scope asks where its name may appear. Linkage asks whether declarations in different scopes or source files denote the same object.

"Storage class" is not shorthand for where RAM is allocated. It groups rules about duration, scope and linkage. The common introductory keywords are auto, register, static and extern; C also defines other storage-class specifiers.

Declaration

Storage duration

Scope

Linkage

Main effect

Block auto int x = 4;

Automatic

Block

None

Creates a fresh object when the block is entered

Block register int y = 3;

Automatic

Block

None

Permits an optimisation hint and restricts address-taking

Block static int calls;

Static

Block

None

Keeps one object for the whole program

File-scope static int bonus = 2;

Static

File

Internal

Keeps the name within one translation unit

File-scope definition int total = 10;

Static

File

External

Defines an object other translation units can declare

Declaration extern int total;

Static

File

External

Refers to the externally linked object defined elsewhere

auto and register: Fresh Automatic Objects on Every Call

A block variable is automatic by default, so writing auto int x = 4; is normally redundant. A register object also has automatic duration. The keyword permits an optimisation hint but does not guarantee a CPU register.

#include <stdio.h>

int product(void) {
    auto int x = 4;
    register int y = 3;
    return x * y;
}

int main(void) {
    printf("%d %d\n", product(), product());
    return 0;
}

The output is:

12 12

Each call creates fresh objects with x = 4 and y = 3, calculates 12, then ends their lifetimes when product returns. Applying & to a register variable violates a C constraint even if the compiler would not place it in a hardware register.

Block-Scope static: One Object That Retains Its Value

A local static name remains limited to its block, but its object survives function returns. This program makes three calls in separate statements so that argument evaluation order cannot distract from the state change.

#include <stdio.h>

int next(void) {
    static int n = 2;
    n += 3;
    return n;
}

int main(void) {
    int a = next();
    int b = next();
    int c = next();
    printf("%d %d %d\n", a, b, c);
    return 0;
}

n is initialised to 2 only once. Call 1 changes it from 2 to 5, so a = 5. Call 2 reuses the same object and changes 5 to 8, so b = 8. Call 3 changes 8 to 11, so c = 11. The output is therefore 5 8 11.

The combination matters: n has block scope, no linkage and static duration. Its name is usable only inside next, while the object remains alive for the next call.

Timeline of a single static int n persisting across three calls, advancing 2 to 5 to 8 to 11 and printing 5 8 11.

File-Scope static and extern: Internal Versus External Linkage

At file scope, int total = 10; defines an object with static duration and external linkage, so another source file may declare and use it. By contrast, static int bonus = 2; has static duration and internal linkage. Its name is limited to the counter.c translation unit and cannot be reached with an extern declaration elsewhere.

In main.c, extern int total; declares the total defined in counter.c; it does not create another counter. Use one external definition and declarations where needed.

One exception matters: extern int total = 10; is a definition because it has an initializer, so do not place it in multiple source files. extern declares an externally linked entity; it does not mean "global variable creation".

Fully Worked Two-File Example Using All Four Keywords

Create counter.c with this code:

#include <stdio.h>

int total = 10;
static int bonus = 2;

void add(int step) {
    static int calls;
    auto int before = total;
    register int delta = step + bonus;

    total += delta;
    ++calls;
    printf("call=%d before=%d delta=%d after=%d\n",
           calls, before, delta, total);
}

Create main.c beside it:

#include <stdio.h>

extern int total;
void add(int step);

int main(void) {
    printf("start=%d\n", total);
    add(3);
    add(5);
    printf("end=%d\n", total);
    return 0;
}

Compile and run both translation units together:

cc -std=c17 -Wall -Wextra main.c counter.c -o storage-demo
./storage-demo

The exact output is:

start=10
call=1 before=10 delta=5 after=15
call=2 before=15 delta=7 after=22
end=22

Before the first call, total = 10 and bonus = 2. Because calls has static storage duration and no explicit initializer, it is zero-initialised, so calls = 0.

For add(3), a fresh before copies 10. A fresh delta becomes step + bonus = 3 + 2 = 5. Updating total gives 10 + 5 = 15, and incrementing the retained calls gives 1.

For add(5), a new before copies the current total, so before = 15. A new delta is 5 + 2 = 7. Updating the same total gives 15 + 7 = 22, and the same calls object advances from 1 to 2. The declaration in main.c refers to this externally linked total, so the final line prints 22.

Two-column diagram: counter.c and main.c share one external total rising 10 to 15 to 22, with bonus internal and calls static.

Initialisation Rules That Change the Output

static int calls; is zero-initialised before execution, so its first ++calls produces 1. An uninitialised automatic object such as auto int value; has an indeterminate value. Reading it is undefined behaviour, with no valid numeric "garbage value" to predict.

An explicitly initialised automatic object is initialised each time its declaration is reached. That is why the fresh before objects receive 10 and then 15 in the two calls. A block-scope static object is initialised once before program start, as shown by n = 2 in next. Finally, extern int total; alone performs no initialisation. The value 10 comes from the single definition int total = 10; in counter.c.

Common Storage-Class Mistakes and Their Fixes

  1. Treating scope and lifetime as synonyms. In add, the name calls is local, but its object lasts for the entire program.

  2. Assuming every static has the same effect. Inside add, static duration lets calls retain its value. At file scope, static also gives bonus internal linkage.

  3. Predicting a number from an uninitialised automatic object. Reading an uninitialised auto or register object is undefined behaviour. Initialise it before use.

  4. Expecting register to guarantee speed or hardware placement. It permits an optimisation hint and prevents address-taking. It promises neither a faster program nor a physical register.

  5. Defining total in both files. Keep one definition in counter.c and write extern int total; in main.c. A file-scope static object such as bonus cannot be imported from another translation unit.

How Questions Test Storage Classes, Then the Short Next Step

Dry-run this before compiling it: int f(void) { static int x = 4; auto int y = 2; x += y; return x; }. Store three calls in separate statements. The persistent x starts at 4 and gains 2 per call, so the results are 6, 8 and 10. y is recreated as 2 on every call, while x survives.

Stable question forms ask you to match a declaration to its duration, scope and linkage, trace repeated calls containing a static local, distinguish a declaration from a definition, or decide whether two source files can name the same object. C programming for teaching and CS exams connects storage-class reasoning to other recurring C concepts and assessment patterns.

In short, auto and register describe automatic block objects. A block static retains state between calls. A file-scope static keeps a name within one translation unit. extern declares an externally linked entity shared with its definition. Build these ideas through the C Language course, continue with the beginner-friendly Complete C Programming course, or browse related paths in Coding Skills.