Understanding Memory in C++
Normally, whenever we hear the term memory, we think of a part of computer hardware that is faster than RAM, or we may think of the place where the CPU keeps the data it is currently working on.
But things are a little different when we talk about memory in C++. In C++, the term covers many concepts related to how our program uses memory to manage the data that the CPU works with.
RAM
Let's first understand what happens when a program gets loaded into memory.
Program memory
┌──────────────────────┐
│ Code │
├──────────────────────┤
│ Global / Static │
├──────────────────────┤
│ Heap │
├──────────────────────┤
│ │
│ Stack │
└──────────────────────┘
Stack: Fast, automatic memory used for local variables and function execution frames. When a function ends, the objects with automatic storage duration in its stack frame are destroyed.
Heap (Free Store): Memory used for dynamic storage. In C++, it can be allocated using
newand released usingdelete. This memory persists until it is released, making manually managed dynamic memory a common source of memory leaks if it is not properly released.Global / Static Data: Stores global and static variables that remain alive for the lifetime of the program.
Code (Text) Segment: A section of the program's memory that holds the compiled machine-code instructions. It is typically read-only.
Code (Text) Segment
This is a read-only segment. The OS does not allow us to write here.
Global / Static Data
Variables
1. Global Declaration
We can allocate data here by declaring variables at global scope. The scope of these variables is global, so we can access them from different parts of the code according to their scope and linkage.
2. Static Allocation
This is one of the most important parts to understand.
Whenever we declare a static variable, it has static storage duration. This means that once it is initialized, it remains alive until the program terminates.
But there is one catch: the scope of a static variable is not necessarily the same as that of a global variable.
It follows the normal rules of scope.
When we declare a static variable inside a function and call that function for the first time, the static variable is initialized and remains alive for the entire program execution. Other local variables in the function have automatic storage duration.
When the function ends, the automatic local variables are destroyed, but the variable with the static keyword remains alive.
When we call the function again, the static variable is not created again. The same variable is used, and it retains its previous state or data, which the function can continue to work with.
But this does not mean we can access this variable outside the function.
3. Static Functions
This is something that needs to be clarified.
static for functions means something different from what we know about static variables.
When we add static to a function at file or namespace scope, it gives the function internal linkage. This means that the function cannot be accessed from other .cpp files.
What About OOP?
Here, the same concept applies to variables.
When we have a static variable inside a class, it is not part of each object. It belongs to the class, and all objects can access and modify the same variable.
For functions, it is different.
Static member functions are associated with the class, but they do not have access to the this pointer.
Note
Static variables are not manually deleted. Their lifetime ends automatically according to the rules of their storage duration, and objects with static storage duration are destroyed during program termination.
Rule of Thumb
Don't use
staticmerely because you want to avoid creating a variable repeatedly. Use it because the variable logically needs a persistent lifetime.
Global Variables
Be more careful with these.
Global Mutable State
anywhere → can modify it
↓
harder to understand who changed it
↓
harder to test/debug
Prefer:
- local variables
- function parameters
- objects
- encapsulated class members
-
const/constexprglobals when appropriate
A global constant is generally much less problematic than a mutable global.
Stack and Heap
Stack
The stack is primarily used for automatic storage associated with function execution.
This is where function-local variables with automatic storage duration are typically stored while their function is executing.
When we have nested functions, we can have multiple function execution frames active on the stack.
It follows the Last In, First Out (LIFO) rule.
int x = 10;
Here, x is a local variable with automatic storage duration if it is declared inside a function.
Heap
The heap is used for dynamic storage.
int* p = new int(10);
Stack
┌──────────────┐
│ p │ ────────────────┐
└──────────────┘ │
↓
┌──────────────┐
│ 10 │ Heap
└──────────────┘
The object created by new has dynamic storage duration.
"Stack is fast, heap is slow."
That's an oversimplification.
The real performance differences come from allocation mechanisms, access patterns, locality, allocator behavior, cache behavior, etc.
Ways of Allocating Memory
1. new
The most direct way:
int* p = new int(10);
delete p;
2. malloc()
C++ can also use the C allocation functions:
int* p = static_cast<int*>(malloc(sizeof(int)));
free(p);
malloc(sizeof(int))
│
▼
Allocate N bytes
│
▼
┌──────────────┐
│ raw memory │
│ │
│ N bytes │
└──────────────┘
▲
│
void*
│
▼
static_cast<int*>
│
▼
int*
│
▼
p
We can then store data at that memory location:
*p = 42;
3. std::allocator
C++ also provides std::allocator for allocating raw storage.
std::allocator<int> alloc;
int* p = alloc.allocate(1);
std::construct_at(p, 10);
std::destroy_at(p);
alloc.deallocate(p, 1);
The basic process is:
allocate
↓
construct
↓
use
↓
destroy
↓
deallocate
Standard Template Library (STL)
The STL provides many tools that help us manage memory and resources.
1. Smart Pointers — Memory Ownership
Defined in <memory>, smart pointers manage dynamically allocated objects and automatically release them according to their ownership rules.
std::unique_ptr: Exclusive ownership. Exactly oneunique_ptrowns the resource. It cannot be copied, only moved.std::shared_ptr: Shared ownership. It uses reference counting to track how manyshared_ptrobjects share ownership of the resource. The resource is deleted when the lastshared_ptrowning it is destroyed.std::weak_ptr: A non-owning reference to an object managed by astd::shared_ptr. It is commonly used to break circular dependencies.
#include <memory>
#include <iostream>
struct Resource {
Resource() {
std::cout << "Acquired\n";
}
~Resource() {
std::cout << "Released\n";
}
};
void example() {
// std::make_unique is preferred for safety and convenience
auto uPtr = std::make_unique<Resource>();
// Shared ownership
auto sPtr1 = std::make_shared<Resource>();
auto sPtr2 = sPtr1; // Reference count increases to 2
} // All resources are automatically released here
2. Automatic Container Memory
Containers like std::vector, std::string, std::map, and std::list manage their underlying dynamic storage automatically.
RAII Guarantee: Allocations happen during construction or insertion, and cleanup happens automatically when the container is destroyed.
Capacity Control: Containers like
std::vectorcan allocate extra space to minimize expensive reallocations.
Best Practices
| Technique | Goal | Action |
|---|---|---|
| Prefer Smart Pointers | Avoid raw new / delete
|
Use std::make_unique or std::make_shared. |
Use reserve() |
Avoid repeated heap allocations | Call vec.reserve() before pushing a large number of items into a std::vector. |
| Pass by Reference | Reduce memory copies | Pass large STL containers as const T& or move them using std::move. |
| Clear Memory Explicitly | Free unused capacity immediately | Use vec.shrink_to_fit() or the swap trick (std::vector<T>().swap(vec)). |
Thanks for reading.
By Xenon.
Top comments (1)
Hello