Kita sudah memegang dua pasangan alokasi-pelepasan, dan sekarang saatnya mempelajari penyakit yang muncul bila satu sisinya terlupa. Memory leak adalah alokasi memori heap yang tidak pernah dilepas dan tidak lagi bisa dijangkau oleh program — memori itu masih resmi terpakai, tetapi tidak ada pointer yang tersisa untuk meng-delete-nya. Satu kebocoran kecil jarang terasa; masalahnya sifatnya kumulatif. Program yang dijalankan berjam-jam, misalnya server atau game, akan menggerus RAM sedikit demi sedikit sampai sistem kehabisan memori atau OS menghentikannya secara paksa. Chapter ini membedah penyebabnya, membuktikan cara mendeteksinya dengan AddressSanitizer, dan menunjuk arah pencegahan yang akan kita pakai sepanjang sisa Section ini.
Apa yang Terjadi Saat Memori Bocor #
Setiap pemanggilan new mencatat satu blok di pembukuan alokator, dan setiap delete menghapus catatan itu. Leak terjadi ketika catatan tetap ada tanpa pernah dihapus sampai program berakhir. Dampaknya merambat ke luar proses: sistem operasi tetap menganggap memori itu dipakai, sehingga aplikasi lain kekurangan ruang. Gejala yang khas dan mudah dikenali:
- Konsumsi memori naik terus selama program berjalan, padahal beban kerjanya stabil.
- Melambat seiring waktu karena alokator semakin sulit mencari blok yang layak.
- Mati di tengah jalan saat permintaan alokasi berikutnya gagal dan melempar
std::bad_alloc.
Menulis Program yang Bocor (untuk Dipelajari) #
Program di bawah ini sengaja bocor: setiap pemanggilan prosesSatuPutaran() mengalokasikan 4000 byte dan melupakan pelepasaannya. Bentuknya seperti bug asli — kode berjalan lancar, output benar, tidak ada peringatan compiler:
#include <iostream>
void prosesSatuPutaran() {
int* data = new int[1000]; // 4000 byte dialokasikan
data[0] = 1;
// ... lupa delete[] data: alokasi bocor setiap pemanggilan
}
int main() {
for (int i = 0; i < 3; ++i) {
prosesSatuPutaran();
}
std::cout << "program selesai (tanpa tanda bahaya)\n";
return 0;
}Setelah loop selesai, program mengembalikan semua stack frame-nya dengan rapi — tetapi 12000 byte heap tetap tercatat terpakai sampai proses berhenti. Pada program kecil ini dampaknya nol; pada server yang menjalankan fungsi itu jutaan kali, kita sedang menulis kebocoran yang dihitung gigabyte.
Deteksi dengan AddressSanitizer #
g++ 14.2 membawa AddressSanitizer (ASan), sebuah tool runtime yang mencatat setiap alokasi dan memeriksa pelepasannya. Saat program berakhir, modul LeakSanitizer dari ASan membandingkan pembukuan dan melaporkan alokasi yang tidak pernah dilepas. Kompilasi program di atas dengan flag sanitizer, lalu jalankan:
g++ -std=c++23 -Wall -Wextra -fsanitize=address kebocoran.cpp -o kebocoran
./kebocoranHasilnya bukan output program, melainkan laporan diagnostik berikut (alamat memori, alamat proses, dan path berbeda di setiap mesin):
=================================================================
==19974==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 12000 byte(s) in 3 object(s) allocated from:
#0 0x7f81f85b48d8 in operator new[](unsigned long)
#1 0x560d7aceb1aa in prosesSatuPutaran()
#2 0x560d7aceb208 in main
SUMMARY: AddressSanitizer: 12000 byte(s) leaked in 3 allocation(s).Laporan ini sangat presisi: 12000 byte terbagi dalam 3 alokasi — persis tiga pemanggilan prosesSatuPutaran() — dan stack trace menunjuk langsung ke baris new[] yang menjadi penyebab. Alternatif lain adalah valgrind dengan perintah valgrind --leak-check=full ./kebocoran, tool klasik yang menganalisis program tanpa perlu kompilasi ulang, meskipun lebih lambat dan biasanya harus dipasang terpisah. ASan cepat dan terintegrasi dengan g++, sehingga menjadi pilihan utama saat development; catatan mendalamnya menanti pada Section Debugging dan Code Quality.
Penyebab Umum Kebocoran #
Leak jarang lahir dari kelalaian yang jujur seperti di atas. Tiga pola yang paling sering kita temui di lapangan:
- Lupa pelepasan: alokasi dibuat, jalur kodenya berbelok, dan baris
deletetidak pernah tertulis. - Early return: fungsi melepas di akhir, tetapi
returndi tengah jalan melewati baris itu — kebocoran lahir tanpa terasa:
void simpan(bool valid) {
char* buffer = new char[256];
if (!valid) {
return; // LEAK: baris delete[] tidak tercapai
}
// ... proses ...
delete[] buffer;
}- Exception:
throwdi tengah fungsi melompati semua baris setelahnya, termasukdelete— pola yang kita tangani khusus pada Chapter Exception-Safe Resource Management.
Semua pola itu berbagi akar yang sama: pelepasan manual bergantung pada kita mengingat seluruh jalur eksekusi yang mungkin. Semakin bercabang program, semakin mustahil mengandalkan ingatan.
Pencegahan: Serahkan pada Objek #
Versi aman dari program yang bocor hanya berbeda satu hal — delete[] dipanggil di jalur yang benar, dan untuk kasus sederhana seperti ini polanya masih mudah dipertahankan:
#include <iostream>
void prosesSatuPutaran() {
int* data = new int[1000]; // 1000 int dialokasikan di heap
long total = 0;
for (int i = 0; i < 1000; ++i) {
data[i] = i;
total += data[i];
}
std::cout << "total: " << total << "\n";
delete[] data; // dilepas di jalur keluar normal
}
int main() {
for (int i = 0; i < 3; ++i) {
prosesSatuPutaran();
}
std::cout << "program selesai tanpa kebocoran\n";
return 0;
}Output yang Dihasilkan:
total: 499500
total: 499500
total: 499500
program selesai tanpa kebocoranProgram aman di atas tetap menuntut kita mengingat satu baris, dan satu baris terlupakan sudah cukup untuk kebocoran. Chapter berikutnya mengangkat idiom yang menghapus beban ingatan itu sepenuhnya: mengelola resource dengan RAII.
Referensi: GCC 14.2 — Instrumentation Options (-fsanitize) , cppreference — RAII .