Episode ini membedah pointer dan reference, pointer arithmetic, alokasi dinamis dengan new dan delete serta smart pointer, pola RAII untuk resource management deterministik, dan bug memori umum seperti use-after-free dan double-free.

Inilah materi yang membedakan C++ dari hampir semua bahasa modern: kalian mengelola memori secara langsung. Kemampuan ini memberi kinerja dan kontrol luar biasa, tetapi juga menuntut tanggung jawab — salah kelola memori berarti crash, kebocoran, atau kerentanan keamanan.
Episode 7 membangun pemahaman menyeluruh tentang memori: apa itu pointer dan reference, bagaimana pointer arithmetic bekerja, bagaimana alokasi dinamis dikelola secara manual dan modern, mengapa RAII adalah pola terpenting di C++, serta bug-bug memori yang harus kalian kenali dan hindari.
Setiap variabel tinggal di alamat memori. Pointer menyimpan alamat itu; reference adalah alias yang mengikat ke variabel lain. Dereference *p membaca nilai di alamat yang ditunjuk:
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 menyimpan alamat x, dan int& r = x membuat alias r ke x. Pointer bisa bernilai nullptr dan bisa diubah menunjuk ke tempat lain; reference selalu terikat dan tidak pernah kosong.
Kombinasi const dan pointer perlu dibaca hati-hati: const int* p berarti pointer ke int konstan, sedangkan int* const p berarti pointer konstan. Fungsi cetak(const std::string& s) menerima string tanpa menyalin dan tanpa mengubahnya — pola default untuk argumen tipe besar.
Pointer bisa dijumlahkan dan dikurangi. Menambah pointer sebesar satu memindahkannya ke elemen berikutnya — bukan byte berikutnya. Besaran langkah ditentukan oleh tipe elemen, jadi p + 1 pada int* maju beberapa byte sesuai ukuran 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
./ptrarithPola for (int* q = arr; q != arr + 4; ++q) adalah iterasi pointer klasik. Di C++ modern, iterator dan range-based for menggantikan pola ini untuk data di kontainer, tetapi pointer arithmetic tetap esensial untuk pemrograman rendah tingkat.
Operator new mengalokasikan memori di heap dan memanggil constructor; delete memanggil destructor dan membebaskan memori. Setiap new wajib berpasangan dengan 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) mengalokasikan satu int dengan nilai 7, dan new int[5] mengalokasikan array. Ingat pasangannya: delete untuk objek tunggal, delete[] untuk array. Mencampurnya adalah undefined behavior.
Alokasi manual rawan: jika delete terlewat saat ada pengecualian atau return lebih awal, terjadi kebocoran. Jika delete dipanggil dua kali pada pointer yang sama, terjadi double-free. Solusi modern: jangan alokasikan manual — gunakan smart pointers dan kontainer STL yang mengelola memori sendiri.
RAII adalah pola inti C++: inisialisasi objek = perolehan resource, penghancuran objek = pelepasan resource. Dengan menempatkan resource di constructor dan melepaskannya di destructor, resource terjamin dibebaskan pada saat yang pasti — bahkan saat exception terjadi.
std::unique_ptr, std::shared_ptr, dan std::weak_ptr menerapkan RAII untuk memori heap:
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) membuat Buffer yang isinya dibebaskan oleh destructor saat buff keluar dari scope. Tidak ada delete manual di main — RAII mengaturnya.
Use-after-free terjadi saat kalian memakai memori setelah dibebaskan. Double-free terjadi saat delete dipanggil dua kali untuk alamat yang sama. Dangling pointer menunjuk ke memori yang sudah tidak valid — misalnya pointer ke variabel lokal yang sudah mati. Memakai memori yang sudah mati adalah undefined behavior, dan compiler dengan -Wall -Wextra akan memperingatkannya. Solusinya: kembalikan value, atau alokasikan dengan make_unique jika objek harus hidup lebih lama.
Info
Deteksi bug memori bisa diotomatisasi. Compile dengan -fsanitize=address untuk mendeteksi use-after-free dan overflow; episode 11 dan 15 akan membahas sanitizer dan Valgrind.
Inti yang harus dibawa pulang:
new berpasangan delete, setiap new[] berpasangan delete[].new dan delete manual.Di episode 8 selanjutnya kita akan membahas data structures dan STL containers — array, std::vector, std::array, dan std::deque, kontainer asosiatif std::set, std::map, dan std::unordered_map, iterator dan algoritma std::algorithm, serta container adaptors seperti stack, queue, dan priority_queue.