Storage Classes in C

A storage class in C determines four fundamental properties of a variable:

  1. Scope — where the variable is visible and accessible
  2. Lifetime — how long the variable exists in memory
  3. Linkage — whether the variable can be accessed from other translation units (files)
  4. 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

  1. Overview and Summary Table
  2. auto Storage Class
  3. register Storage Class
  4. static Storage Class
  5. extern Storage Class
  6. Scope, Lifetime, and Linkage Explained
  7. Memory Segments and Storage Classes
  8. Common Patterns and Use Cases
  9. Common Mistakes
  10. Interview Questions

1. Overview and Summary Table

Storage ClassKeywordScopeLifetimeDefault ValueLinkageStorage Location
AutomaticautoBlock (local)Block durationGarbageNoneStack
RegisterregisterBlock (local)Block durationGarbageNoneCPU register (hint)
Static (local)staticBlock (local)Program duration0NoneData/BSS segment
Static (global)staticFileProgram duration0InternalData/BSS segment
ExternalexternFile/GlobalProgram duration0ExternalData/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

  1. Cannot take the address: Since registers don’t have memory addresses, & is illegal.
register int x = 10;
printf("%p", &x);   // Compilation error!
  1. Compiler may ignore the hint: Modern compilers are excellent at register allocation. They often ignore register and make their own decisions.

  2. C++17 deprecated: In C++, register is 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: 0 if not explicitly initialized
  • Linkage: None
  • Storage: Data segment (if initialized) or BSS segment (if zero-initialized)

Use Cases

  1. Function call counters:
int get_next_id() {
    static int next_id = 1000;
    return next_id++;
}
  1. 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);
}
  1. 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

  1. Encapsulation: Hide implementation details from other files
  2. Internal state: Maintain file-private global state
  3. Prevent name collisions: Multiple files can have static variables 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 TypeVisibility
BlockWithin { } where declared
Function prototypeWithin the parameter list
FileThroughout the translation unit
GlobalAcross all translation units (with external linkage)

Lifetime

Lifetime determines when memory is allocated and deallocated.

LifetimeCreatedDestroyed
AutomaticBlock entryBlock exit
StaticProgram startProgram termination
Dynamicmalloc/callocfree

Linkage

Linkage determines whether a name refers to the same entity across translation units.

LinkageMeaningExample
ExternalName is visible across filesGlobal variables without static
InternalName is visible only in its filestatic globals and functions
NoneName is unique to its scopeLocal 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.
  • auto is the default for locals — exists only within its block.
  • register is a hint for CPU register storage — cannot take its address.
  • static local: initialized once, retains value across calls, still local scope.
  • static global: file-private, prevents external linkage.
  • extern declares a variable defined elsewhere — no memory allocated.
  • Static variables are zero-initialized; auto variables contain garbage.
  • Use static on globals and helper functions for encapsulation.

Related Topics: Memory Layout, Scope and Lifetime, Functions, Variables