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 | Automatic | Block | None | Creates a fresh object when the block is entered |
Block | Automatic | Block | None | Permits an optimisation hint and restricts address-taking |
Block | Static | Block | None | Keeps one object for the whole program |
File-scope | Static | File | Internal | Keeps the name within one translation unit |
File-scope definition | Static | File | External | Defines an object other translation units can declare |
Declaration | 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 12Each 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.

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-demoThe exact output is:
start=10
call=1 before=10 delta=5 after=15
call=2 before=15 delta=7 after=22
end=22Before 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.

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
Treating scope and lifetime as synonyms. In
add, the namecallsis local, but its object lasts for the entire program.Assuming every
statichas the same effect. Insideadd, static duration letscallsretain its value. At file scope,staticalso givesbonusinternal linkage.Predicting a number from an uninitialised automatic object. Reading an uninitialised
autoorregisterobject is undefined behaviour. Initialise it before use.Expecting
registerto guarantee speed or hardware placement. It permits an optimisation hint and prevents address-taking. It promises neither a faster program nor a physical register.Defining
totalin both files. Keep one definition incounter.cand writeextern int total;inmain.c. A file-scope static object such asbonuscannot 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.




