Storage Classes in C
A storage class in C determines four fundamental properties of a variable:
- Scope — where the variable is visible and accessible
- Lifetime — how long the variable exists in memory
- Linkage — whether the variable can be accessed from other translation units (files)
- Default initialization — what value the variable holds if not explicitly initialized
C provides four storage class specifiers: auto, register, static, and extern. Understanding these is essential for writing correct, efficient, and modular C programs.
Table of Contents
- Overview and Summary Table
- auto Storage Class
- register Storage Class
- static Storage Class
- extern Storage Class
- Scope, Lifetime, and Linkage Explained
- Memory Segments and Storage Classes
- Common Patterns and Use Cases
- Common Mistakes
- Interview Questions
1. Overview and Summary Table
| Storage Class | Keyword | Scope | Lifetime | Default Value | Linkage | Storage Location |
|---|---|---|---|---|---|---|
| Automatic | auto | Block (local) | Block duration | Garbage | None | Stack |
| Register | register | Block (local) | Block duration | Garbage | None | CPU register (hint) |
| Static (local) | static | Block (local) | Program duration | 0 | None | Data/BSS segment |
| Static (global) | static | File | Program duration | 0 | Internal | Data/BSS segment |
| External | extern | File/Global | Program duration | 0 | External | Data/BSS segment |
2. auto Storage Class
auto is the default storage class for all local variables. It is so rarely written explicitly that many programmers never use the keyword at all.
void function() {
auto int x = 10; // explicit auto (rare)
int y = 20; // implicit auto (common)
}
Properties
- Scope: Block-local — visible only within the block where declared
- Lifetime: Block duration — created on block entry, destroyed on block exit
- Default value: Garbage (indeterminate)
- Linkage: None
- Storage: Stack
Behavior
void counter() {
int count = 0; // auto by default
count++;
printf("%d ", count);
}
int main() {
counter(); // prints 1
counter(); // prints 1
counter(); // prints 1
}
Each call creates a fresh count initialized to 0. The previous value is lost.
When to Use
Almost never write auto explicitly. It exists primarily for historical reasons and for disambiguation in rare cases.
3. register Storage Class
The register keyword is a hint to the compiler that a variable will be heavily used and should be stored in a CPU register for faster access.
void sum_loop() {
register int i; // hint: keep i in a register
int sum = 0;
for (i = 0; i < 1000000; i++)
sum += i;
}
Properties
- Scope: Block-local
- Lifetime: Block duration
- Default value: Garbage
- Linkage: None
- Storage: CPU register (if the compiler honors the request)
Important Limitations
- Cannot take the address: Since registers don’t have memory addresses,
&is illegal.
register int x = 10;
printf("%p", &x); // Compilation error!
-
Compiler may ignore the hint: Modern compilers are excellent at register allocation. They often ignore
registerand make their own decisions. -
C++17 deprecated: In C++,
registeris deprecated and removed as a storage class specifier. C still supports it.
Modern Relevance
With optimizing compilers, register is largely obsolete. The compiler’s register allocator typically outperforms manual hints. However, it remains valid C and may be useful in:
- Embedded systems with simple compilers
- Code readability (documenting intent)
- Very old or non-optimizing compilers
4. static Storage Class
static is the most versatile and frequently used storage class. Its behavior depends entirely on whether it is applied to a local variable or a global variable.
static Local Variables
A static local variable retains its value between function calls. It is initialized only once, at program startup.
void counter() {
static int count = 0; // initialized only ONCE
count++;
printf("%d ", count);
}
int main() {
counter(); // prints 1
counter(); // prints 2
counter(); // prints 3
}
Properties
- Scope: Block-local (still only visible within the function)
- Lifetime: Program duration (exists for the entire program execution)
- Default value:
0if not explicitly initialized - Linkage: None
- Storage: Data segment (if initialized) or BSS segment (if zero-initialized)
Use Cases
- Function call counters:
int get_next_id() {
static int next_id = 1000;
return next_id++;
}
- Memoization / caching:
int fib(int n) {
static int memo[100] = {0};
if (n <= 1) return n;
if (memo[n] != 0) return memo[n];
return memo[n] = fib(n-1) + fib(n-2);
}
- Singleton pattern:
Config* get_config() {
static Config config = {0};
static int initialized = 0;
if (!initialized) {
load_config(&config);
initialized = 1;
}
return &config;
}
static Global Variables
When applied to global variables (or functions), static restricts visibility to the current translation unit (source file).
// file1.c
static int internal_counter = 0; // only visible in file1.c
static void helper() { // only callable from file1.c
internal_counter++;
}
void public_function() {
helper();
}
Properties
- Scope: File-local
- Lifetime: Program duration
- Default value:
0 - Linkage: Internal
- Storage: Data/BSS segment
Use Cases
- Encapsulation: Hide implementation details from other files
- Internal state: Maintain file-private global state
- Prevent name collisions: Multiple files can have
staticvariables with the same name
5. extern Storage Class
The extern keyword declares a variable or function that is defined in another translation unit. It does not allocate storage — it simply tells the compiler “this exists somewhere else.”
// file1.c
int shared_count = 0; // definition (allocates storage)
void increment() {
shared_count++;
}
// file2.c
extern int shared_count; // declaration (no storage allocated)
void print_count() {
printf("%d
", shared_count);
}
Properties
- Scope: File or global
- Lifetime: Program duration
- Default value:
0(in the defining translation unit) - Linkage: External
- Storage: Data/BSS segment (in the defining file)
Common Pattern: Header Files
// config.h
extern int debug_level; // declaration
extern char *app_name; // declaration
// config.c
#include "config.h"
int debug_level = 0; // definition
char *app_name = "MyApp"; // definition
// main.c
#include "config.h"
int main() {
debug_level = 3; // uses the variable defined in config.c
printf("%s
", app_name);
}
extern with Function Declarations
Function declarations are implicitly extern, so the keyword is optional:
extern void helper(); // explicit
void helper(); // implicit — same meaning
6. Scope, Lifetime, and Linkage Explained
Scope
Scope determines where a variable name is visible.
int global = 10; // file scope
void func() {
int local = 20; // block scope
{
int inner = 30; // nested block scope
printf("%d %d %d
", global, local, inner);
}
// inner is not visible here
}
| Scope Type | Visibility |
|---|---|
| Block | Within { } where declared |
| Function prototype | Within the parameter list |
| File | Throughout the translation unit |
| Global | Across all translation units (with external linkage) |
Lifetime
Lifetime determines when memory is allocated and deallocated.
| Lifetime | Created | Destroyed |
|---|---|---|
| Automatic | Block entry | Block exit |
| Static | Program start | Program termination |
| Dynamic | malloc/calloc | free |
Linkage
Linkage determines whether a name refers to the same entity across translation units.
| Linkage | Meaning | Example |
|---|---|---|
| External | Name is visible across files | Global variables without static |
| Internal | Name is visible only in its file | static globals and functions |
| None | Name is unique to its scope | Local variables |
7. Memory Segments and Storage Classes
Understanding where variables live in memory helps explain their behavior.
+------------------+ High Address
| Stack | auto, register locals
| (grows down) |
+------------------+
| Heap | malloc/calloc/realloc
| (grows up) |
+------------------+
| BSS | uninitialized static/globals (zeroed)
+------------------+
| Data | initialized static/globals
+------------------+
| Text | program code (read-only)
+------------------+ Low Address
Where Each Storage Class Lives
int global = 10; // Data segment
static int s_global; // BSS segment (zero-initialized)
void func() {
int local = 5; // Stack
static int s_local; // BSS segment (zero-initialized)
register int reg; // CPU register (or stack if no register available)
}
8. Common Patterns and Use Cases
Pattern 1: Static Counter
int get_serial_number() {
static int serial = 1000;
return serial++;
}
Pattern 2: File-Private Helper Functions
// math_utils.c
static double square(double x) { // only visible in this file
return x * x;
}
double hypotenuse(double a, double b) {
return sqrt(square(a) + square(b));
}
Pattern 3: Module-Level State
// logger.c
static FILE *log_file = NULL;
static int log_level = 0;
void logger_init(const char *filename, int level) {
log_file = fopen(filename, "a");
log_level = level;
}
void logger_write(const char *msg) {
if (log_file && log_level > 0)
fprintf(log_file, "%s
", msg);
}
Pattern 4: extern in Headers
// globals.h
#ifndef GLOBALS_H
#define GLOBALS_H
extern int app_running;
extern int debug_mode;
#endif
9. Common Mistakes
Mistake 1: Thinking static Makes a Variable Global
void func() {
static int x = 0; // Still local scope!
x++;
}
// x is NOT accessible outside func
static extends lifetime, not scope.
Mistake 2: Confusing Declaration and Definition
// In a header file — WRONG!
int shared_var; // This is a DEFINITION, not a declaration
// Every file that includes this gets its own copy!
// Correct way in header:
extern int shared_var; // Declaration only
// Definition in exactly ONE .c file:
int shared_var = 0;
Mistake 3: Taking Address of register Variable
register int x = 10;
int *p = &x; // Compilation error
Mistake 4: Assuming auto Variables Are Zero-Initialized
void func() {
int x; // Garbage value!
static int y; // Guaranteed 0
printf("%d %d
", x, y); // x is unpredictable
}
10. Interview Questions
Q1: What is the difference between a static local and a static global variable?
A static local variable has block scope but program lifetime. It retains its value between function calls. A static global variable has file scope and program lifetime — it is invisible to other translation units.
Q2: What is the default storage class of a local variable?
auto. It is implicit and rarely written.
Q3: Can a static local variable be accessed outside its function?
No. static extends lifetime, not scope. The variable is still local to its block.
Q4: Does extern allocate memory?
No. extern is purely a declaration. Memory is allocated only where the actual definition appears.
Q5: What is the default value of a static variable?
0 (or 0.0 for floats, NULL for pointers, false for bools). Static variables are zero-initialized if no explicit initializer is given.
Q6: What happens if you declare a variable as both static and extern?
This is invalid. static means internal linkage; extern means external linkage. They are contradictory.
Q7: Why use static on global functions?
To limit their visibility to the current file, preventing name collisions and enforcing encapsulation.
Q8: What is the output?
void func() {
static int x;
int y;
printf("%d %d
", x++, y++);
}
int main() {
func();
func();
}
Output: 0 garbage then 1 garbage. x is static (starts at 0, persists). y is auto (garbage each time, then incremented).
Key Takeaways
- Storage classes control scope, lifetime, linkage, and initialization.
autois the default for locals — exists only within its block.registeris a hint for CPU register storage — cannot take its address.staticlocal: initialized once, retains value across calls, still local scope.staticglobal: file-private, prevents external linkage.externdeclares a variable defined elsewhere — no memory allocated.- Static variables are zero-initialized; auto variables contain garbage.
- Use
staticon globals and helper functions for encapsulation.
Related Topics: Memory Layout, Scope and Lifetime, Functions, Variables