Lesson 1 of 7
Every object in a C++ program lives in one of three places. Which one it gets is decided by how you declared it, not by what it holds.
| | Lifetime | Managed by | Size known | |---|---|---|---| | Static | whole program | compiler | at compile time | | Stack | its enclosing block | compiler | at compile time | | Heap | until you free it | you | at run time |
Static storage
Globals and static locals exist from before main starts until after it ends:
#include <iostream>
int global_counter = 0;
void count_calls() {
static int calls = 0; // initialised once, on the first call
++calls;
++global_counter;
std::cout << "call " << calls << "\n";
}
int main() {
count_calls();
count_calls();
count_calls();
std::cout << "global: " << global_counter << "\n";
return 0;
}
call 1
call 2
call 3
global: 3
static int calls = 0; is initialised once, not on every call. The variable survives between
calls because it is not on the stack.
Globals are convenient and mostly a mistake: anything in the program can change them, so a bug
has no small set of suspects. static locals are narrower and occasionally justified.
The stack
Ordinary local variables live on the stack. The compiler allocates them when the block is entered and releases them when it exits:
#include <iostream>
void inner() {
int x = 2;
std::cout << "inner x = " << x << "\n";
}
void outer() {
int x = 1;
std::cout << "outer x before = " << x << "\n";
inner();
std::cout << "outer x after = " << x << "\n";
}
int main() {
outer();
return 0;
}
outer x before = 1
inner x = 2
outer x after = 1
Two variables named x, both alive at once, neither disturbing the other. Each function call
gets its own stack frame holding its parameters and locals. Calling pushes a frame; returning
pops it.
That is why this is fast: allocating a stack variable costs the compiler adjusting one number. It is also why locals vanish on return, which lesson 8.5 turns into a bug.
The stack has a fixed size, and you can exhaust it
#include <iostream>
int depth(int n) {
// Each call adds a frame. Deep enough recursion runs out of stack.
if (n <= 0) {
return 0;
}
return 1 + depth(n - 1);
}
int main() {
std::cout << depth(1000) << "\n";
return 0;
}
1000
A thousand frames is fine. A few million is not — the stack is typically 1–8 MB, and exceeding it is a stack overflow, which crashes the program. Lesson 10.6's recursion depth limits come from exactly this.
Note also what you cannot do on the stack:
int n = read_from_user();
int values[n]; // not standard C++ — the size must be known at compile time
The stack needs sizes at compile time. When you do not have them, you need the heap.
The heap
The heap is memory you request while running, and it stays yours until you give it back:
#include <iostream>
int main() {
int count = 5; // pretend this came from the user
int* values = new int[count]; // ask for count ints at run time
for (int i = 0; i < count; ++i) {
values[i] = i * i;
}
for (int i = 0; i < count; ++i) {
std::cout << values[i] << " ";
}
std::cout << "\n";
delete[] values; // give it back
return 0;
}
0 1 4 9 16
Three things differ from the stack:
- The size is a run-time value.
countcould have come from a file or a user. - The lifetime is yours to decide. It outlives the block it was created in.
- You must return it. Forget, and you have a leak (lesson 8.4).
That third point is the whole problem, and the reason lesson 8.3 shows you new/delete and
then tells you not to use them.
Which one a declaration gets
#include <iostream>
#include <vector>
#include <string>
int global = 1; // static storage
int main() {
int local = 2; // stack
static int persistent = 3; // static storage, despite being written here
int* raw = new int(4); // the int is on the heap; `raw` itself is on the stack
std::vector<int> v = {5, 6}; // v is on the stack; its elements are on the heap
std::string s = "hello"; // same idea
std::cout << global << local << persistent << *raw << v[0] << s.size() << "\n";
delete raw;
return 0;
}
123455
The two interesting lines are the last two. std::vector and std::string are stack objects
that own heap memory: the small handle lives on the stack, the data lives on the heap, and the
destructor frees it for you when the block ends.
That pattern is the answer to almost every memory question in C++, and lesson 8.2 gives it a name.
Try it
Write a function returning the sum of an array. Call it with a stack array, then with a heap
array of a size decided at run time. Free the heap one. Then add a static counter that
reports how many times the function ran.
Checkpoint
- Static storage lives for the whole program: globals and
staticlocals. - Stack holds ordinary locals; fast, automatic, sized at compile time, gone at block exit.
- Heap is requested at run time, sized at run time, and released only when you say so.
- Each call gets a stack frame; the stack is finite and deep recursion overflows it.
std::vectorandstd::stringare stack handles owning heap data — the pattern to copy.
Questions about this lesson
Log in to ask a question.