9.0 What This Teaches
C++ gives direct access to memory. Understanding how memory works is essential
for writing correct, efficient programs. This tutorial covers:
- Stack allocation vs heap allocation
- Pointers: holding addresses, dereferencing, null pointers
- References: aliases that cannot be rebound or null
- Manual heap management with
new and delete
- RAII: tying resource lifetime to object lifetime
9.1 Stack and Heap
Every variable lives in one of two memory regions:
| Stack | Heap |
| Allocation | Automatic on declaration | Explicit with new |
| Deallocation | Automatic on scope exit | Explicit with delete |
| Size | Fixed at compile time | Dynamic at runtime |
| Speed | Very fast | Slower (allocator overhead) |
| Failure mode | Stack overflow | Memory leak if delete forgotten |
Prefer stack allocation. Use the heap (or smart pointers) only when you need
dynamic size or lifetime that outlasts the current scope.
9.2 Pointers
int x = 10;
int* p = &x; // p holds the address of x
std::cout << *p; // dereference: read the value at the address
*p = 20; // modify x through the pointer
std::cout << x; // 20
int* null_p = nullptr; // safer than NULL or 0
if (null_p != nullptr) {
std::cout << *null_p; // never reached
}
Always initialize pointers. Use nullptr rather than
0 or NULL for null pointers - it is type-safe.
9.3 References
int a = 5;
int& ref = a; // ref is an alias for a; must be initialized; cannot be rebound
ref = 99;
std::cout << a; // 99
// const reference: read-only alias; can bind to temporaries
const int& cref = 42; // ok: 42 is kept alive by the const reference
References are simpler and safer than pointers for most use cases: they cannot
be null, cannot be rebound, and do not need dereferencing syntax.
9.4 new and delete
int* p = new int(42); // allocate one int on the heap
std::cout << *p; // 42
delete p; // free the memory
p = nullptr; // avoid dangling pointer
int* arr = new int[10]; // allocate array
arr[0] = 1;
delete[] arr; // free array - must match new[]
Every new must be paired with exactly one delete;
every new[] with delete[]. In modern C++ prefer
std::unique_ptr and std::vector to avoid manual
management entirely.
9.5 RAII
class FileHandle {
public:
FileHandle(const std::string& name) : name_(name) {
std::cout << "opened: " << name_ << "\n";
}
~FileHandle() {
std::cout << "closed: " << name_ << "\n";
}
private:
std::string name_;
};
void demo() {
FileHandle fh("log.txt"); // "opened: log.txt"
// ... use fh ...
} // "closed: log.txt" - destructor fires automatically, even if an exception is thrown
RAII (Resource Acquisition Is Initialization) is the C++ idiom for safe
resource management. Acquire the resource in the constructor, release it
in the destructor. The resource is always released when the object goes out
of scope - even through exceptions.
9.6 Example - All Together
// Memory - stack vs heap, pointers, references, new/delete, RAII.
#include <iostream>
#include <string>
class FileHandle {
public:
FileHandle(const std::string& name) : name_(name) {
std::cout << "opened: " << name_ << "\n";
}
~FileHandle() { std::cout << "closed: " << name_ << "\n"; }
private:
std::string name_;
};
int main() {
// pointers
int x = 10;
int* p = &x;
*p = 20;
std::cout << "x=" << x << "\n";
// heap
int* hp = new int(42);
std::cout << "heap=" << *hp << "\n";
delete hp;
hp = nullptr;
// references
int a = 5;
int& ref = a;
ref = 99;
std::cout << "a=" << a << "\n";
// RAII
{
FileHandle fh("log.txt");
} // destructor fires here
return 0;
}
x=20
heap=42
a=99
opened: log.txt
closed: log.txt
9.7 Exercise
Exercise
- Allocate an array of 5
double values on the heap, fill it
with squares (0.0, 1.0, 4.0, 9.0, 16.0), print them, then free the
array correctly.
- Write a function
increment(int& n) that increments its
argument. Call it and confirm the caller's variable changed.
- Create an
RAII class that prints "lock acquired" in its
constructor and "lock released" in its destructor. Create an instance in
a block and observe the output order.
9.8 Common Mistakes
Memory leak: forgetting delete
void leak() {
int* p = new int(42);
// ... forgot delete p; ...
} // p goes out of scope; memory is never freed
Use std::unique_ptr instead of raw new.
Dangling pointer
int* p = new int(42);
delete p;
std::cout << *p; // undefined behavior: p points to freed memory
Set pointers to nullptr after delete.
delete[] vs delete mismatch
int* arr = new int[10];
delete arr; // undefined behavior: should be delete[]
9.9 Key Terms
| Term | Meaning |
| pointer (T*) | Variable holding a memory address; dereference with * |
| reference (T&) | Alias for an existing variable; cannot be null or rebound |
| nullptr | Null pointer constant; type-safe replacement for NULL |
| new / delete | Allocate / free a single object on the heap |
| new[] / delete[] | Allocate / free an array on the heap |
| dangling pointer | Pointer to freed or out-of-scope memory; dereferencing is undefined behavior |
| memory leak | Heap memory allocated but never freed |
| RAII | Resource Acquisition Is Initialization: destructor guarantees cleanup |