Kenapa Meminta Memori Saat Program Berjalan
Sejauh ini setiap variabel yang kamu buat tinggal di stack: ukurannya diketahui saat kompilasi dan ia dihancurkan otomatis saat scope-nya berakhir. Cara itu cepat dan aman, tetapi tidak bisa menangani kasus ketika kamu baru tahu berapa banyak memori yang dibutuhkan setelah program berjalan: buffer yang ukurannya ditentukan input pengguna, struktur yang harus hidup lebih lama daripada fungsi yang membuatnya, atau graph yang bentuknya belum diketahui sebelumnya.
Untuk kasus-kasus itu C++ membiarkanmu mengalokasikan memori dari heap (disebut juga free store) dengan new, lalu mengembalikannya dengan delete. Ekspresi new memesan satu blok, menjalankan constructor, lalu mengembalikan pointer ke blok itu. Ini dibangun langsung di atas pointer yang kamu lihat di halaman sebelumnya.
Variabel p sendiri tinggal di stack; ia hanya sebuah pointer. int yang ditunjuknya tinggal di heap dan tetap hidup sampai kamu melakukan delete padanya, berapa pun banyaknya scope yang datang dan pergi.
Stack vs Heap
Perbedaan inilah alasan new ada, jadi ada baiknya dibuat konkret.
void demo() {
int a = 10; // on the stack - gone when demo() returns
int* b = new int(10); // 'b' on the stack, the int it points to on the heap
} // 'a' destroyed; the heap int LEAKS - never deleted
Perbedaan utamanya:
- Stack: masa hidup otomatis, sangat cepat, ukurannya terbatas (biasanya beberapa MB), dan dibebaskan untukmu saat scope berakhir.
- Heap: masa hidup manual, sedikit lebih lambat, ukurannya besar, dan dibebaskan hanya saat kamu memanggil
delete.
Pertukarannya adalah fleksibilitas dengan tanggung jawab: memori heap hidup persis selama yang kamu mau, tetapi kamulah yang harus ingat untuk membebaskannya.
Mengalokasikan Array dengan new[]
Kalau kamu butuh blok yang panjangnya ditentukan saat program berjalan, pakai bentuk array new T[n]. Bentuk ini mengembalikan pointer ke elemen pertama, dan kamu membebaskannya dengan pasangannya, delete[].
Aturannya ketat dan gampang dilanggar: memori dari new dibebaskan dengan delete, dan memori dari new[] dibebaskan dengan delete[]. Mencampurnya, misalnya delete arr pada sesuatu yang dialokasikan dengan new[], adalah undefined behavior, meskipun kelihatannya berfungsi di komputermu.
Tiga Bug Klasik
Pengelolaan memori manual punya sekumpulan kecil kesalahan yang menjadi penyebab sebagian besar bug heap. Belajarlah mengenali ketiganya.
1. Memory leak: kamu tidak pernah memanggil delete. Bloknya tetap dipesan selamanya. Tidak berbahaya kalau sekali, fatal kalau terjadi di dalam loop.
void leaky() {
int* p = new int(5);
// ... no delete ...
} // p is gone; the heap int is now unreachable AND unfreed
2. Dangling pointer: kamu memakai memori setelah membebaskannya. Pointer-nya masih menyimpan alamat lama, tetapi memori itu bukan milikmu lagi.
3. Double free: kamu melakukan delete pada blok yang sama dua kali. Ini merusak catatan internal heap dan biasanya membuat program crash.
int* p = new int(1);
delete p;
delete p; // double-free - undefined behavior, often a crash
Mengisi pointer dengan nullptr setelah menghapusnya menjinakkan dangling pointer sekaligus double free: dereference pada nullptr langsung crash (mudah di-debug), dan delete nullptr secara eksplisit adalah operasi aman yang tidak melakukan apa-apa.
Siklus Alokasi, Pakai, Bebaskan yang Realistis
Kalau digabungkan, inilah bentuk pengelolaan manual yang benar: alokasikan, pakai, bebaskan tepat sekali, dan jangan sentuh pointer-nya sesudah itu.
Perhatikan bahwa delete u melakukan dua hal untuk tipe class: ia menjalankan destructor objek terlebih dulu, lalu melepaskan memori mentahnya. Urutan itu penting begitu objekmu memiliki resource sendiri.
Jebakan yang halus: kalau sebuah exception dilempar di antara new dan delete, delete tidak pernah berjalan dan terjadi kebocoran. Membungkus setiap alokasi dengan try/catch untuk menanganinya itu membosankan dan rawan salah, dan justru itulah masalah yang diselesaikan halaman berikutnya.
Berikutnya: Smart Pointer
Sekarang kamu sudah melihat seluruh harga dari mengelola memori secara manual: setiap new adalah janji untuk melakukan delete nanti, dan satu pembebasan yang terlewat, dobel, atau terlalu cepat adalah undefined behavior. C++ modern hampir tidak pernah membuat janji itu secara manual. Halaman berikutnya memperkenalkan smart pointer, yaitu std::unique_ptr dan std::shared_ptr: objek yang memiliki alokasi heap dan memanggil delete untukmu secara otomatis saat keluar dari scope, sehingga ketiga bug klasik itu ditangani oleh compiler dan RAII atas namamu.
Pertanyaan yang sering diajukan
Apa perbedaan new dan delete di C++?
new mengalokasikan memori di heap saat program berjalan dan mengembalikan pointer ke memori itu; delete membebaskan memori yang dialokasikan dengan new. Setiap new harus dipasangkan dengan tepat satu delete, kalau tidak terjadi kebocoran memori. Untuk array, pakai new[] dengan delete[].
Apa yang terjadi kalau lupa memanggil delete di C++?
Terjadi memory leak: blok heap tetap dipesan selama programmu hidup meskipun tidak ada lagi yang menunjuk ke sana. Satu kebocoran biasanya tidak berbahaya, tetapi kebocoran di dalam loop atau di layanan yang berjalan lama terus membesar sampai program kehabisan memori lalu crash.
Apakah sebaiknya memakai new dan delete langsung di C++ modern?
Jarang. Utamakan container seperti std::vector atau smart pointer (std::unique_ptr, std::shared_ptr) yang membebaskan memori secara otomatis. new/delete mentah tetap layak dipahami karena smart pointer membungkusnya, tetapi di kode sehari-hari keduanya adalah sumber kebocoran memori dan dangling pointer.