Storage Classes in C
Storage Classes in C

There are four storage class in c language.

Let's look into each and context of their uses.

auto — The Default Local Variable

The auto keyword is the default storage class for any variable declared inside a function or block. You almost never write it explicitly because the compiler assumes it when no other specifier is given.

#include <stdio.h>

void demo(void) {
    auto int x = 10;   /* same as: int x = 10; */
    int  y = 20;       /* implicitly auto */
    printf("%d %d\n", x, y);
}   /* x and y are destroyed here */

Scope: within the block where declared.
Lifetime: created when the block is entered, destroyed when it exits.
Storage: stack frame.
Default value: undefined (garbage).

register — Hint for CPU Register Allocation

The register keyword asks the compiler to place the variable in a CPU register (e.g., r0–r12 on ARM) rather than in RAM. This eliminates a memory load/store on every access, giving a speed boost for tight loops.

#include <stdio.h>

int sum_array(const int *arr, int n) {
    register int i, total = 0;   /* hint: keep these in registers */
    for (i = 0; i < n; i++)
        total += arr[i];
    return total;
}

Important rules:

/* C — address of register not allowed */
#include <stdio.h>
int main(void) {
    register int a = 10;
    // printf("%p", &a);  /* error: address of register variable requested */
    printf("%d\n", a);
    return 0;
}

static — Persistent Local Variables and File Scope

The static keyword has two distinct uses depending on where it appears:

1. Static local variable

The variable is allocated once in the BSS/data segment and survives across function calls. It is initialised only the first time the function is entered.

#include <stdio.h>

void counter(void) {
    static int count = 0;   /* initialised once; persists across calls */
    count++;
    printf("called %d time(s)\n", count);
}

int main(void) {
    counter();   /* called 1 time(s) */
    counter();   /* called 2 time(s) */
    counter();   /* called 3 time(s) */
    return 0;
}

2. Static global variable / function (internal linkage)

When applied to a global variable or function, static restricts its visibility to the translation unit (the .c file). This is the standard way to create "private" module-level state in C.

/* module.c */
static int error_count = 0;          /* not visible outside this file */
static void log_error(const char *msg) { /* internal helper */
    error_count++;
    /* ... */
}
void public_api(void) { log_error("oops"); } /* visible everywhere */

extern — Share Variables Across Translation Units

extern declares that a variable or function exists but is defined (memory allocated) in another translation unit. No storage is reserved by the declaration itself.

/* config.c — definition: memory is allocated here */
int baud_rate = 115200;

/* main.c — declaration: tells the compiler it exists elsewhere */
extern int baud_rate;

int main(void) {
    /* Use baud_rate — linker resolves the reference to config.c */
    return 0;
}

Common patterns:

extern int a;         /* declaration only — no memory allocated */
int    b;             /* definition — memory allocated             */
a = 10;               /* ERROR — a has no definition yet           */
extern int d = 0;     /* definition + declaration (initialiser present) */

Comparison Table

Storage Class Scope Lifetime Default Init Linkage Storage Location
auto Block Block duration Undefined None Stack
register Block Block duration Undefined None CPU register / stack
static (local) Block Entire program Zero None BSS / Data segment
static (global) File (translation unit) Entire program Zero Internal BSS / Data segment
extern Program-wide Entire program Zero (at definition) External BSS / Data segment

Memory Layout — Stack vs Data Segment

Understanding where a variable lives helps you avoid common bugs like returning a pointer to a local variable:

  [ High address ]
  ┌─────────────────────────────────┐
  │           Stack                 │  ← auto / register variables
  │   grows downward ↓              │     destroyed on function return
  ├─────────────────────────────────┤
  │           Heap                  │  ← malloc / calloc
  │   grows upward ↑                │
  ├─────────────────────────────────┤
  │      BSS segment                │  ← static / extern uninitialised
  │   (zero-filled by OS loader)    │     live for entire program
  ├─────────────────────────────────┤
  │      Data segment               │  ← static / extern initialised
  │   (loaded from binary)          │     live for entire program
  ├─────────────────────────────────┤
  │      Text (code) segment        │  ← read-only instructions
  [ Low address ]
#include <stdio.h>

static int s_global = 0;     /* data segment — initialised */
static int s_uninit;         /* BSS segment  — zero-filled */

int *danger(void) {
    int local = 42;
    return &local;           /* WRONG: stack frame gone after return */
}

int *safe(void) {
    static int persistent = 42;
    return &persistent;      /* OK: data segment, survives return */
}

Embedded Systems Use Cases

On microcontrollers (ARM Cortex-M, AVR, RISC-V) memory is scarce and deterministic behaviour matters more than on a desktop. Storage classes become engineering decisions, not just style choices.

ScenarioBest ChoiceReason
ISR state counter (e.g., tick count) static local + volatile Persists between ISR invocations; volatile prevents register caching
HAL configuration struct shared across files extern declaration + one .c definition Single source of truth, avoids linker conflicts
Private module state (not exported in header) static global Prevents accidental access from other modules
Loop index in tight DSP inner loop register (or rely on -O2) Keeps index in CPU register, reduces load/store cycles
Bootloader vector table pointer (read-only ROM) const + static Placed in flash, zero RAM cost at runtime

As an embedded developer, choosing the right storage class is a real memory-budget decision. A static variable that lives forever consumes precious RAM on a device with 4 KB of SRAM. Design intentionally.


📬 Get new articles in your inbox

Deep dives on SystemC, C++, and embedded systems — no spam, unsubscribe any time.

No spam, unsubscribe any time. Privacy Policy

Aditya Gaurav

Aditya Gaurav

Embedded systems engineer specializing in SystemC, ARM architecture, and C/C++ internals. Writing deep technical dives for VLSI and embedded engineers.