Skip to main content

Data Lifetime and Memory Segments

The computer has finite resources for "remembering" things. So, you can't just keep asking it to remember data without at some point also telling it that it's ok to forget the data. Otherwise, at some point it will be completely full of stuff you told it to remember, and it just won't be able to remember anything new, even if most of that data isn't even accessible through variables anymore (this is called a memory leak). The duration of time between remembering the data (allocating space for it in memory) and telling your computer that it can forget the data (freeing/deallocating the space) is called the lifetime of the memory.

Date Lifetime 

Three categories for the lifetime of memory in order to understand how variables are stored:

  • Global: This memory is around for the entire lifetime of your program. Generally this is a fixed amount of space that is allocated right when the program starts, and it cannot grow.

  • Local: The memory is allocated for you when entering a particular portion of the program, and automatically freed for you when exiting that part. This is denoted with curly braces {}, so it could be the scope of an...

      • entire function

      • if statement

      • loop

      • Anything inside curly braces {}

    Inside a block scope have limited lifetime and visibility. Outside that block, those variables no longer exist.

  • Dynamic: This memory has a lifetime which is unknown at the time of allocation. The program explicitly asks for a specific size of memory, and then that memory lives until the program explicitly says the memory is no longer needed.

    • In C++, this is done via malloc or new and then the corresponding free or delete calls. 

      Many languages (like Python, but not C++) uses a "Garbage Collector" takes control of the lifetime of dynamically allocated memory. You only ever call new and then there is a background algorithm always running which tracks which memory is still being used and frees it for you. This avoids most causes of memory leaks, but it cost performance. C++ puts the burden on the programmer to do it manually (and correctly) to be more performant. 

Static, Stack, and Heap

With data lifetime into consideration. Lets introduce the Static, Stack, and Heap

1. Static Variables

Storage Duration:

  • Static Storage Duration: The variable is allocated when the program starts and deallocated when the program ends, persisting for the entire lifetime of the program.

Scope:

  • Global Static: When declared outside of functions or classes, static variables have file scope (internal linkage). They are visible only in the file or translation unit they are defined in.
  • Local Static: When declared inside a function, static variables have function scope, but their value persists across function calls.

Memory Location:

  • Data Segment (either .bss for uninitialized or .data for initialized variables) of the program’s memory.

Initialization:

  • Global Static Variables: Automatically initialized to zero (or nullptr for pointers) if not explicitly initialized.
  • Local Static Variables: Initialized the first time the code execution passes through their declaration (e.g., in a function). If not explicitly initialized, they default to zero.
#include <iostream>

static int globalStatic = 42; // Static global variable (file scope)

void function() {
    static int localStatic = 10; // Static local variable (function scope)
    localStatic++;
    std::cout << "Local Static: " << localStatic << std::endl;
}

int main() {
    function(); // Prints 11
    function(); // Prints 12
    return 0;
}

2. Heap (Free Store) Variables

This is limited by your system's RAM memory, which can be exhausted by programs of huge demands (usually games and scientific tools)

Storage Duration:

  • Dynamic Storage Duration: Variables on the heap are dynamically allocated using new (or malloc in C) and persist until they are explicitly deallocated using delete (or free).

Scope:

  • The scope of a pointer to a heap-allocated variable depends on where the pointer is declared, but the memory allocated on the heap persists independently of the pointer’s scope until manually freed.

Memory Location:

  • Heap is managed dynamically.

Initialization:

  • Non-initialized heap variables are left with indeterminate values unless you explicitly initialize them.
  • Heap-allocated objects are initialized through constructors if they are class objects.
#include <iostream>

int main() {
    int* heapVar = new int(5); // Dynamically allocated on the heap
    std::cout << "Heap Variable: " << *heapVar << std::endl;

    delete heapVar; // Must be manually deallocated to avoid memory leak
    return 0;
}

3. Stack Variables

Stack will pretty much never exhaust it unless 

  • you define absurdly large objects on the stack (e.g. an array of milions of objects)
  • you recurse too deeply (usually as a result of a bug of infinite recursion or unnecessarily large stack frame size)

Stack Frame:

  • Definition: A stack frame is a structured block of memory on the stack that is created when a function is called. It holds:
    • Local variables
    • Function parameters
    • Return address (where to return after the function call)
    • Saved registers and other context information
  • Lifetime: The stack frame is automatically created when the function is invoked and destroyed when the function exits.
  • No "Heap Frames" or "Static Frames": While stack frames provide a structured way to manage function calls, heap memory is more flexible and does not use frames. Instead, it uses blocks of memory allocated as needed, allowing for dynamic data structures and sizes.

Storage Duration:

  • Automatic Storage Duration: Variables declared on the stack (inside a function or block scope) are automatically allocated when their block of code is entered and deallocated when the block is exited.

Scope:

  • Stack variables have local scope, which means they are visible only inside the function or block in which they are declared.

Memory Location:

  • Stack, which is a part of memory where automatic variables are stored.

Initialization:

  • Uninitialized primitive types (like int, char, etc.) in C++ have indeterminate values.
  • Local variables of class types are initialized through constructors.
  • Always good practice to initialize stack variables to avoid undefined behavior.
#include <iostream>

void function() {
    int stackVar = 10;  // Allocated on the stack
    std::cout << "Stack Variable: " << stackVar << std::endl;
} // 'stackVar' is destroyed here when function exits

int main() {
    function();
    return 0;
}

Memory Segments relation with Hardware

Whether they are stored on the stack, heap, or in static/global memory—are typically stored in RAM (Random Access Memory). However, they occupy different regions of RAM based on their storage duration and allocation mechanism.

|------------------------------|
|        Text Segment          |  <-- Program instructions (code)
|------------------------------|
|        Initialized Data      |  <-- Global and static variables with initialization
|------------------------------|
|     Uninitialized Data (.bss)|  <-- Global/static variables without explicit initialization
|------------------------------|
|        Heap                  |  <-- Dynamically allocated memory
|  (grows upwards)             |
|------------------------------|
|        Stack                 |  <-- Local variables, function calls
|  (grows downwards)           |
|------------------------------|