Static Memory (The Persistent Storage)

cardimg

In C++, Static Memory refers to variables that are allocated once when the program starts and persist until the program ends. They are not created/destroyed repeatedly like Stack variables, nor are they manually managed like Heap variables.

The keyword static has two distinct meanings depending on where it is used:

  1. Lifetime: The data survives forever (Persistence).

  2. Visibility: The data is hidden from other files (Internal Linkage) OR shared by all objects of a class (Shared Scope).

The Three Contexts of static

A. Static Local Variables (Function Scope)

Behavior: Preserves value across function calls.

Initialization: Initialized only the first time control passes through the declaration.

Destruction: Destructed when main() exits.

void count_events() {
    // 1. Initialized only ONCE (Thread-safe in C++11+)
    static int counter = 0; 
    
    counter++;
    printf("Events: %d\n", counter);
}
// Call 1 -> "Events: 1"
// Call 2 -> "Events: 2" (It remembers!)
  • Embedded Use: replacing global variables. If a variable is only used by one function but needs to remember its state (e.g., a filter's previous input), make it static local. It keeps the global namespace clean.

B. Static Class Members (Class Scope)

Behavior: Shared by all instances of the class. It belongs to the Class, not the Object.

Storage: Stored in a single fixed memory location, regardless of how many objects you create (0, 1, or 100).

class Driver {
public:
    static int active_drivers; // Declaration (Blueprint)
    
    Driver() { active_drivers++; }
    ~Driver() { active_drivers--; }
    
    // Static Methods: Can run without an object. 
    // Can ONLY access static variables.
    static int getCount() { return active_drivers; }
};

// Definition (Allocation): MUST be in .cpp file (Global Scope)
int Driver::active_drivers = 0;

C. Static Global Variables (File Scope)

Behavior: Limits visibility to this specific file only (Internal Linkage).

Purpose: Encapsulation at the file level. Prevents name collisions with variables in other .cpp files.

// File1.cpp
static int buffer[100]; // Only File1 sees this

// File2.cpp
int buffer[100]; // Totally different variable. No Linker Error.
  • Note: In modern C++, Anonymous Namespaces (namespace { ... }) are preferred over static for file-scope hiding, but static is still ubiquitous in embedded code.

Memory Layout (Deep Dive)

Static variables live in specific memory segments, handled by the Startup Code (crt0.s) before main() begins.

Segment

Description

Example

Startup Cost

.bss

Uninitialized or Zero-initialized statics.

static int x;


static int y = 0;

Fast. Startup code just memsets the block to 0.

.data

Initialized statics (Non-zero).

static int x = 42;

Slower. Startup code must memcpy values from Flash to RAM.

.rodata

Read-Only statics (Constants).

static const int x = 5;

Zero RAM. stays in Flash (ROM).

  • Optimization Tip: Prefer Zero-initialization (= 0). It saves Flash space and reduces boot time compared to Non-Zero initialization.

Advanced Initialization Details

A. The "Static Initialization Order Fiasco"

The order in which global/static variables are initialized across different translation units (.cpp files) is undefined.

  • If FileA.cpp has static int A = 10;

  • And FileB.cpp has static int B = A + 1;

  • The compiler might initialize B before A, resulting in B = 0 + 1 = 1 (Garbage) instead of 11.

  • Fix: Use the Construct On First Use idiom (Lazy Singleton).

B. Modern C++ Features (C++17)

inline static: Solves the annoyance of defining variables in the .cpp file.

class Config {
public:
    // C++17: Memory allocated immediately. No .cpp definition needed.
    inline static int timeout = 1000; 
};

constexpr static: Used for compile-time constants.

class Math {
public:
    // Stored in Flash (ROM). No RAM usage.
    static constexpr float PI = 3.14159f; 
};

Relevance in Embedded/Firmware

A. Determinism & Safety

Dynamic Memory (malloc/new) is non-deterministic. It can fail (return NULL) or take variable time (heap walking).

Static Memory is allocated by the linker.

  1. Fail-Safe: If you run out of RAM, the Linker fails (Build Error), not the runtime (Crash). You know before you ship.

  2. Timing: Accessing static memory is O(1) and cache-friendly.

B. Placement New (Static Class Objects)

How to use C++ Objects (Constructors) without the Heap?

You reserve a static buffer and build the object there.

#include <new>

// 1. Reserve Static RAM (BSS)
alignas(UART) uint8_t uart_memory[sizeof(UART)];

void system_init() {
    // 2. Construct object in static RAM
    UART* ptr = new (uart_memory) UART(9600);
    
    // Object lives forever. No delete needed (unless shutting down).
}

C. Thread-Safe Initialization ("Magic Statics")

Since C++11, static local variable initialization is guaranteed to be thread-safe.

If two threads call getInstance() at the same time, the compiler inserts hidden locks (mutexes) to ensure the object is constructed exactly once.

  • Warning: In very low-level embedded (No OS, simple startup), verify your compiler supports this or if flags like -fno-threadsafe-statics are enabled.

Common Pitfalls & Checklist

Pitfall

Explanation

Prevention

Linker Error (Undefined Reference)

You declared static int x; in the class but didn't define it in .cpp.Add int Class::x = 0; in source or use inline static (C++17).

Concurrency / Race Conditions

Static variables are shared globals. If an ISR and Main Loop both write to it, data corruption occurs.Always use volatile if shared with ISR, and wrap in Critical Sections (disable interrupts).

Large .data Segment

Initializing large arrays (static int arr[1000] = {1, 2...}) copies data from Flash to RAM at boot.Mark large lookup tables as const or constexpr so they stay in Flash (.rodata).

Hidden Dependencies

One static variable depends on another static variable in a different file.Refactor to use local statics (Functions returning values) to control order.

 

 

 

Concept understood? Let's apply and learn for real

Practice now