This episode dissects pointers and references, pointer arithmetic, dynamic allocation with new and delete along with smart pointers, the RAII pattern for deterministic resource management, and common memory bugs like use-after-free and double-free.

This is the material that sets C++ apart from almost every modern language: you manage memory directly. This capability gives extraordinary performance and control, but it also demands responsibility — mismanaging memory means crashes, leaks, or security vulnerabilities.
Episode 7 builds a thorough understanding of memory: what pointers and references are, how pointer arithmetic works, how dynamic allocation is managed manually and modernly, why RAII is the most important pattern in C++, and the memory bugs you must recognize and avoid.
Every variable lives at a memory address. A pointer stores that address; a reference is an alias bound to another variable. Dereferencing *p reads the value at the pointed-to address:
cat > ptr.cpp <<'EOF'
#include <iostream>
int main() {
int x = 42;
int* p = &x;
int& r = x;
std::cout << "x = " << x << "\n";
std::cout << "Alamat x = " << p << "\n";
std::cout << "Dereference = " << *p << "\n";
*p = 99;
r = 100;
std::cout << "x setelah diubah = " << x << "\n";
}
EOF
g++ -std=c++20 ptr.cpp -o ptr
./ptrint* p = &x stores the address of x, and int& r = x creates an alias r for x. A pointer can hold nullptr and can be changed to point elsewhere; a reference is always bound and never empty.
Combining const and pointers needs careful reading: const int* p means a pointer to a const int, while int* const p means a const pointer. The function cetak(const std::string& s) accepts a string without copying and without modifying it — the default pattern for large-type arguments.
Pointers can be incremented and decremented. Adding one to a pointer moves it to the next element — not the next byte. The step size is determined by the element type, so p + 1 on an int* advances by as many bytes as the size of int:
cat > ptrarith.cpp <<'EOF'
#include <iostream>
int main() {
int arr[]{10, 20, 30, 40};
int* p = arr;
for (int i = 0; i < 4; ++i) {
std::cout << *(p + i) << " ";
}
std::cout << "\n";
for (int* q = arr; q != arr + 4; ++q) {
std::cout << *q << " ";
}
std::cout << "\n";
}
EOF
g++ -std=c++20 ptrarith.cpp -o ptrarith
./ptrarithThe pattern for (int* q = arr; q != arr + 4; ++q) is classic pointer iteration. In modern C++, iterators and range-based for replace this pattern for container data, but pointer arithmetic remains essential for low-level programming.
The new operator allocates memory on the heap and calls the constructor; delete calls the destructor and frees the memory. Every new must pair with a delete:
cat > manual.cpp <<'EOF'
#include <iostream>
int main() {
int* p = new int(7);
std::cout << *p << "\n";
delete p;
int* arr = new int[5];
for (int i = 0; i < 5; ++i) {
arr[i] = i * i;
}
std::cout << arr[3] << "\n";
delete[] arr;
}
EOF
g++ -std=c++20 manual.cpp -o manual
./manualnew int(7) allocates a single int with the value 7, and new int[5] allocates an array. Remember the pairs: delete for a single object, delete[] for an array. Mixing them is undefined behavior.
Manual allocation is error-prone: if delete is skipped when an exception or early return happens, a leak occurs. If delete is called twice on the same pointer, a double-free happens. The modern solution: don't allocate manually — use smart pointers and STL containers that manage memory themselves.
RAII is the core C++ pattern: initializing an object = acquiring a resource, destroying the object = releasing the resource. By acquiring resources in the constructor and releasing them in the destructor, resources are guaranteed to be freed at a definite point — even when an exception occurs.
std::unique_ptr, std::shared_ptr, and std::weak_ptr apply RAII to heap memory:
cat > raii.cpp <<'EOF'
#include <iostream>
#include <memory>
class Buffer {
public:
explicit Buffer(size_t n) : data_(new int[n]) {}
~Buffer() { delete[] data_; }
int* data() { return data_; }
private:
int* data_;
};
int main() {
auto buff = std::make_unique<Buffer>(10);
buff->data()[0] = 5;
std::cout << buff->data()[0] << "\n";
}
EOF
g++ -std=c++20 raii.cpp -o raii
./raiistd::make_unique<Buffer>(10) creates a Buffer whose contents are freed by the destructor when buff goes out of scope. There's no manual delete in main — RAII handles it.
Use-after-free happens when you use memory after it's been freed. Double-free happens when delete is called twice for the same address. A dangling pointer points to memory that's no longer valid — for example, a pointer to a local variable that has already died. Using dead memory is undefined behavior, and a compiler with -Wall -Wextra will warn about it. The solution: return a value, or allocate with make_unique if the object must outlive the scope.
Info
Memory bug detection can be automated. Compile with -fsanitize=address to detect use-after-free and overflows; episodes 11 and 15 will discuss sanitizers and Valgrind.
Here's what to take away:
new pairs with delete, every new[] pairs with delete[].new and delete.In the next episode, episode 8, we'll discuss data structures and STL containers — arrays, std::vector, std::array, and std::deque, the associative containers std::set, std::map, and std::unordered_map, iterators and the std::algorithm algorithms, as well as container adaptors like stack, queue, and priority_queue.