Objek yang masa hidupnya melampaui fungsi tempatnya dibuat perlu dialokasikan pada memori bebas, dan operator new beserta delete adalah pasangan C++ untuk pekerjaan itu. Topik ini membahas alokasi satu objek, alokasi array dinamis, kegagalan alokasi, serta risiko yang menyertai pointer pemilik mentah sebelum smart pointer diperkenalkan pada topik penutup modul ini.
new dan delete untuk satu objek#
Ekspresi new int{24} mengalokasikan memori seukuran int, menginisialisasinya dengan 24, dan mengembalikan alamatnya. Memori hasil new tidak terikat pada satu fungsi apa pun, sehingga objeknya tetap hidup sampai delete dijalankan atas alamat itu. Pasangan new dan delete adalah satu kesatuan tanggung jawab, sehingga setiap jalur eksekusi yang mengalokasikan wajib berujung pada pelepasannya.
Program berikut menuntaskan satu siklus lengkap, yaitu alokasi, pemakaian, pelepasan, dan penetralan pointer.
#include <iostream>
int main() {
int* p = new int{24};
std::cout << "Nilai awal: " << *p << '\n';
*p = *p + 8;
std::cout << "Nilai setelah diubah: " << *p << '\n';
delete p;
p = nullptr;
std::cout << "Objek dinamis sudah dilepas.\n";
return 0;
}Nilai awal: 24
Nilai setelah diubah: 32
Objek dinamis sudah dilepas.Program di atas memperlihatkan siklus yang utuh tanpa membaca memori apa pun setelah pelepasan. Penugasan p = nullptr; setelah delete menutup kemungkinan pointer terpakai lagi secara tidak sengaja, dan kelalaian seputar pelepasannya dibahas lebih dalam pada topik bug memori. Inisialisasi dengan kurung kurawal pada new int{24} memastikan objek berisi nilai yang diketahui sejak awal, sehingga tidak ada pembacaan atas memori yang belum diisi.
Objek dinamis menempati wilayah memori yang berbeda dari variabel biasa. Variabel lokal hidup di atas tumpukan (stack) dan lenyap saat lingkupnya berakhir, sedangkan objek hasil new hidup di memori bebas (heap) sampai dilepas secara eksplisit. Karena itu objek dinamis dapat dikembalikan dari fungsi tanpa risiko alamat tidak sah. Konsekuensinya, tanggung jawab pelepasan ikut berpindah kepada pihak yang menerima alamatnya.
Array dinamis#
Ekspresi new int[n] mengalokasikan n elemen berurutan dan mengembalikan alamat elemen pertamanya, dengan n yang boleh ditentukan saat program berjalan. Bentuk inilah yang membedakannya dari array biasa, yang panjangnya harus diketahui saat kompilasi. Pelepasannya menggunakan delete[], bukan delete, karena bentuk alokasi dan bentuk pelepasan harus dipasangkan secara tepat.
#include <iostream>
int main() {
int n = 5;
int* data = new int[n]{0};
for (int i = 0; i < n; ++i) {
data[i] = (i + 1) * 10;
}
int jumlah = 0;
for (int i = 0; i < n; ++i) {
jumlah += data[i];
}
std::cout << "Isi array: ";
for (int i = 0; i < n; ++i) {
if (i > 0) {
std::cout << ' ';
}
std::cout << data[i];
}
std::cout << '\n';
std::cout << "Jumlah: " << jumlah << '\n';
delete[] data;
data = nullptr;
return 0;
}Isi array: 10 20 30 40 50
Jumlah: 150Array di atas diakses lewat indeks biasa karena pointer hasil new[] berperilaku seperti array yang meluruh, sebagaimana dibahas pada topik aritmetika pointer. Panjang n tidak tersimpan pada pointer, sehingga program yang mengelola array dinamis perlu menyimpan panjangnya pada variabel tersendiri. Pelepasannya memakai delete[] data, dan setiap elemen yang punya destructor akan dihancurkan satu per satu saat pelepasan.
Inisialisasi new int[n]{0} mengisi seluruh elemen dengan nol sejak alokasi. Bentuk tanpa inisialisasi, yaitu new int[n], meninggalkan isi elemen sebagai nilai sisa yang tidak boleh dibaca sebelum ditulis. Aturannya sama seperti variabel biasa, sehingga alokasi yang diikuti penulisan penuh boleh tanpa inisialisasi, sedangkan alokasi yang langsung dibaca wajib diinisialisasi.
Pada kode sehari-hari, std::vector menggantikan sebagian besar pemakaian array dinamis manual. Vector mengelola alokasi, pelepasan, dan panjangnya sendiri, sehingga program tidak perlu menyimpan dua hal terpisah, yaitu pointer dan panjang. Pengenalan new[] tetap penting karena kode warisan dan fondasi pustaka banyak memakainya, dan karena mekanismenya menjelaskan apa yang vector kerjakan di belakang layar.
Kegagalan alokasi#
Permintaan memori dapat gagal, misalnya saat memori yang tersedia tidak cukup, dan perilaku bawaan new atas kegagalan itu adalah melempar exception std::bad_alloc. Program yang belum menangani exception akan berhenti, sehingga permintaan alokasi yang berukuran besar sebaiknya diperhitungkan sejak awal. Alternatifnya adalah bentuk new (std::nothrow) dari header <new>, yang mengembalikan nullptr alih-alih melempar, sehingga hasilnya dapat diperiksa dengan pola pemeriksaan null yang sudah dibahas pada topik nullptr.
Bentuk new (std::nothrow) mengubah kontrak kegagalan menjadi pemeriksaan biasa. Hasilnya nullptr saat memori tidak cukup, sehingga program dapat menanggapi kegagalan dengan percabangan, misalnya menurunkan ukuran permintaan atau menyimpan pekerjaannya ke disk. Pilihan antara exception dan nothrow adalah keputusan desain, dan yang terpenting adalah salah satunya ditangani, bukan dibiarkan tanpa penanganan.
Risiko pointer pemilik mentah#
Pointer yang bertanggung jawab melepas objeknya sendiri disebut pointer pemilik (owning pointer), dan bentuk mentahnya menumpuk seluruh risiko pengelolaan memori pada program. Setiap pengembalian lebih awal, exception, atau percabangan yang melompati delete menyisakan kebocoran, sedangkan setiap salinan pointer membuka peluang pelepasan ganda. Semakin rumit sebuah program, semakin panjang daftar jalur eksekusinya, sehingga audit manual semakin mahal.
Masalah ini bukan sekadar kelalaian menulis delete. Fungsi yang mengalokasikan lalu mengembalikan nilai lebih awal sebelum baris delete membocorkan memori setiap kali jalur itu dilalui, dan exception yang terlempar di tengah pekerjaan menimbulkan akibat yang sama. Masalah inilah yang mendorong pemindahan tanggung jawab pelepasan ke objek, gagasan yang dirangkum pada topik pratinjau smart pointer sebagai penutup modul ini.
Kesalahan umum#
Alamat hasil new yang disimpan pada variabel lokal hilang saat fungsinya berakhir. Apabila delete tidak pernah dijalankan, memori itu tetap tertahan sampai program berakhir tanpa ada yang merujuknya, dan kondisi inilah yang disebut kebocoran. Pemeriksaan rutin yang sehat adalah memastikan setiap new memiliki tepat satu delete pada setiap jalur eksekusinya.
Pelepasan juga hanya boleh ditujukan ke alamat hasil new. Menjalankan delete atas alamat variabel biasa adalah undefined behavior, karena memori tersebut bukan bagian dari memori bebas yang dikelola new dan delete. Pembedaan ini menegaskan bahwa alamat dari operator & dan alamat hasil new berasal dari wilayah memori yang berbeda, meskipun keduanya sama-sama dapat disimpan pada pointer.
Ringkasan#
newmengalokasikan objek yang tetap hidup sampaideletedijalankan, sedangkannew[]besertadelete[]menjadi pasangannya untuk array dinamis.- Panjang array dinamis tidak tersimpan pada pointer, sehingga program perlu menyimpannya tersendiri, dan
std::vectormenutup kebutuhan ini pada kode sehari-hari. - Kegagalan alokasi melempar
std::bad_alloc, atau mengembalikannullptrbila memakai bentuknew (std::nothrow). - Pointer pemilik mentah menumpuk seluruh tanggung jawab pelepasan pada program, dan topik pratinjau smart pointer menawarkan jalur yang lebih aman.
Referensi#
- new expression — cppreference.com — Ekspresi new, inisialisasinya, dan perilaku kegagalan alokasi.
- delete expression — cppreference.com — Ekspresi delete dan delete[] beserta syarat pemakaiannya.
- Dynamic memory allocation with new and delete — LearnCpp — Alokasi objek tunggal dan kebiasaan pelepasannya.
- Dynamically allocating arrays — LearnCpp — Alokasi array dinamis beserta jebakannya.