Pointer tidak tahu apakah objek yang ditunjuknya masih ada. Begitu kita menyimpan alamat, tanggung jawab menjaga agar objek itu tetap hidup berpindah ke programmer — dan bila objek sudah tiada, pointer yang menyimpan alamatnya berubah menjadi dangling pointer: pointer yang menggantung tanpa sasaran yang sah. Dereference pada dangling pointer adalah undefined behavior, dan inilah salah satu kategori bug paling berbahaya di C++ karena bisa berjalan diam-diam bertahun-tahun. Chapter ini membedah dua penyebab utamanya dan tiga lapis mitigasi yang kita pakai di kode modern.
Penyebab 1: Menunjuk Variabel Lokal yang Sudah Selesai #
Variabel lokal lahir dan mati bersama bloknya — mekanisme yang kita pelajari pada Chapter Scope dan Lifetime Variabel. Alamat variabel lokal bisa kita simpan, tetapi setelah blok berakhir, memori tempat variabel itu berada diklaim ulang untuk keperluan lain. Pointer yang masih menyimpan alamatnya kini menunjuk tempat kosong:
int* buatAngka() {
int lokal = 7;
return &lokal; // DANGLING: lokal selesai saat fungsi kembali
}
int main() {
int* p = buatAngka(); // p menunjuk variabel yang sudah tidak hidup
int v = *p; // UNDEFINED BEHAVIOR: membaca memori yang tidak sah
}Compiler modern benar-benar mengingatkan pola ini saat kompilasi. g++ 14.2 memberi peringatan berikut untuk fungsi buatAngka di atas:
warning: address of local variable 'lokal' returned [-Wreturn-local-addr]
note: declared here
int lokal = 7;Peringatan -Wreturn-local-addr muncul karena compiler melihat alamat variabel lokal dikembalikan dari fungsi. Sayangnya, ingatan compiler sebatas pola yang terlihat di satu fungsi: bila alamatnya dibawa keluar lewat jalur yang lebih berliku (disimpan di variabel global, dikirim ke fungsi lain), peringatan ini tidak muncul. Jangan pernah berharap compiler menangkap semua kasusnya — pencegahan tetap di tangan kita: kembalikan nilai dengan cara biasa (copy), atau teruskan objek penampung ke fungsi lewat parameter.
Penyebab 2: Menunjuk Memori yang Sudah di-delete #
Penyebab kedua melibatkan memori dinamis. Ringkas konteksnya: operator new menyewa memori di heap yang hidup melewati batas blok, dan operator delete mengembalikannya ke sistem — keduanya dibahas tuntas pada Section Manajemen Memori. Setelah delete, memori itu tidak lagi milik kita, tetapi pointer yang menyimpan alamatnya tetap berisi alamat lama:
int* p = new int(42);
delete p; // memori dikembalikan ke sistem
int v = *p; // UNDEFINED BEHAVIOR: use-after-delete
Kasus ini disebut use-after-delete. Ia lebih licik daripada kasus variabel lokal karena tidak ada peringatan compiler sama sekali — secara sintaksis semuanya sah. Pada program kecil, *p bahkan sering masih mengembalikan 42 karena memori belum dipakai ulang; pada program besar yang sama, nilai yang terbaca bisa berupa data milik objek lain, dan menulis ke situ berarti merusak data tanpa jejak. Perilaku yang tampak “berjalan normal” itulah yang membuatnya menumpuk bertahun-tahun sebelum muncul sebagai bug yang sulit ditelusuri.
Mitigasi: Nol-kan, Periksa, dan Delegasikan #
Ada tiga lapis pertahanan yang saling melengkapi. Lapis pertama dan paling dasar: segera nol-kan pointer setelah delete, sehingga status “tanpa sasaran” tercatat secara eksplisit dan bisa diperiksa. Berikut pola amannya yang bisa kita jalankan:
#include <iostream>
int main() {
// Alokasi dinamis dibahas tuntas pada Section Manajemen Memori;
// di sini new/delete dipakai hanya untuk mendemonstrasikan pola aman.
int* p = new int(42);
std::cout << "Nilai setelah alokasi : " << *p << "\n";
delete p; // memori dikembalikan ke sistem
p = nullptr; // mitigasi utama: pointer langsung di-nol-kan
if (p != nullptr) {
std::cout << "Pointer masih terpakai: " << *p << "\n";
} else {
std::cout << "Pointer sudah kosong, aman diperiksa\n";
}
return 0;
}Output yang Dihasilkan:
Nilai setelah alokasi : 42
Pointer sudah kosong, aman diperiksaBaris p = nullptr; mengubah situasi dari “pointer yang menggantung tanpa tanda” menjadi “pointer kosong yang jujur”. Dereference nullptr tetap undefined behavior seperti yang dibahas pada Chapter nullptr, tetapi sekarang setiap penggunaan sebelum dereference dapat memeriksa p != nullptr terlebih dulu — dan itulah yang dilakukan if di program di atas: cabang dereference tidak dieksekusi, program mencetak statusnya, lalu selesai dengan aman.
Lapis kedua: atur pemilikannya dengan disiplin — satu pointer yang jelas bertanggung jawab atas satu alokasi, dan pointer lain hanya “menumpang membaca”. Lapis ketiga, dan yang paling dianjurkan di kode modern: jangan kelola manual sama sekali. Smart pointer std::unique_ptr dan std::shared_ptr pada Section Manajemen Memori membebaskan memori otomatis saat tidak lagi dipakai, sehingga kategori bug ini praktis hilang dari kode yang kita tulis.
Dengan dua penyebab dan tiga mitigasinya, kita kini punya peta lengkap pointer yang bermasalah. Chapter berikutnya merangkum seluruh kategori itu — null, uninitialized, dangling, dan wild — menjadi satu panduan pencegahan yang bisa dipakai sebagai checklist.
Referensi: cppreference — delete expression , cppreference — undefined behavior .