Saya belajar C # pertama, dan sekarang saya mulai dengan C ++. Seperti yang saya mengerti, operator new
di C ++ tidak mirip dengan yang ada di C #.
Bisakah Anda menjelaskan alasan kebocoran memori dalam kode sampel ini?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
Jawaban:
Apa yang terjadi
Saat Anda menulis,
T t;
Anda membuat objek bertipeT
dengan durasi penyimpanan otomatis . Ini akan dibersihkan secara otomatis ketika keluar dari ruang lingkup.Saat Anda menulis,
new T()
Anda membuat objek bertipeT
dengan durasi penyimpanan dinamis . Itu tidak akan dibersihkan secara otomatis.Anda harus meneruskan pointer ke sana
delete
untuk membersihkannya:Namun, contoh kedua Anda lebih buruk: Anda mendereferensi pointer, dan membuat salinan objek. Dengan cara ini Anda kehilangan pointer ke objek yang dibuat dengan
new
, sehingga Anda tidak pernah bisa menghapusnya bahkan jika Anda mau!Apa yang harus Anda lakukan
Anda harus memilih durasi penyimpanan otomatis. Butuh objek baru, cukup tulis:
Jika Anda memang membutuhkan durasi penyimpanan dinamis, simpan pointer ke objek yang dialokasikan dalam objek durasi penyimpanan otomatis yang menghapusnya secara otomatis.
Ini adalah ungkapan umum yang menggunakan nama RAII yang tidak terlalu deskriptif ( Resource Acquisition Is Inisialisasi ). Saat Anda memperoleh sumber daya yang perlu dibersihkan, Anda menempelkannya pada objek dengan durasi penyimpanan otomatis sehingga Anda tidak perlu khawatir untuk membersihkannya. Ini berlaku untuk sumber daya apa pun, baik itu memori, membuka file, koneksi jaringan, atau apa pun yang Anda suka.
Benda ini
automatic_pointer
sudah ada dalam berbagai bentuk, saya baru saja memberikannya untuk memberi contoh. Kelas yang sangat mirip ada di perpustakaan standar yang disebutstd::unique_ptr
.Ada juga yang lama (pra-C ++ 11) bernama
auto_ptr
tetapi sekarang sudah ditinggalkan karena memiliki perilaku penyalinan yang aneh.Dan kemudian ada beberapa contoh yang lebih cerdas, seperti
std::shared_ptr
, yang memungkinkan banyak pointer ke objek yang sama dan hanya membersihkannya ketika pointer terakhir dihancurkan.sumber
*p += 2
, seperti yang Anda lakukan dengan pointer normal. Jika itu tidak kembali dengan referensi, itu tidak akan meniru perilaku pointer normal, yang menjadi maksud di sini.Penjelasan langkah demi langkah:
Jadi pada akhir ini, Anda memiliki objek di heap tanpa pointer ke sana, jadi tidak mungkin untuk menghapus.
Sampel lain:
adalah kebocoran memori hanya jika Anda lupa dengan
delete
memori yang dialokasikan:Di C ++ ada objek dengan penyimpanan otomatis, yang dibuat di stack, yang secara otomatis dibuang, dan objek dengan penyimpanan dinamis, di heap, yang Anda alokasikan dengan
new
dan diharuskan untuk membebaskan diri Anda dengandelete
. (Ini semua kira-kira dimasukkan)Pikirkan bahwa Anda harus memiliki
delete
untuk setiap objek yang dialokasikannew
.EDIT
Kalau dipikir-pikir itu,
object2
tidak harus menjadi kebocoran memori.Kode berikut hanya untuk menyampaikan maksud, itu ide yang buruk, jangan pernah suka kode seperti ini:
Dalam hal ini, karena
other
dilewatkan oleh referensi, itu akan menjadi objek tepat yang ditunjuk olehnew B()
. Oleh karena itu, mendapatkan alamatnya dengan&other
dan menghapus pointer akan membebaskan memori.Tapi saya tidak bisa cukup menekankan hal ini, jangan lakukan ini. Itu hanya di sini untuk menyampaikan maksud.
sumber
Diberi dua "objek":
Mereka tidak akan menempati lokasi yang sama dalam memori. Dengan kata lain,
&a != &b
Menetapkan nilai satu ke yang lain tidak akan mengubah lokasi mereka, tetapi akan mengubah kontennya:
Secara intuitif, penunjuk "objek" bekerja dengan cara yang sama:
Sekarang, mari kita lihat contoh Anda:
Ini menetapkan nilai
new A()
untukobject1
. Nilainya adalah pointer, artinyaobject1 == new A()
, tetapi&object1 != &(new A())
. (Perhatikan bahwa contoh ini bukan kode yang valid, ini hanya untuk penjelasan)Karena nilai pointer dipertahankan, kita dapat membebaskan memori yang ditunjukkannya:
delete object1;
Karena aturan kami, ini berperilaku sama sepertidelete (new A());
yang tidak memiliki kebocoran.Untuk Anda contoh kedua, Anda menyalin objek menunjuk-ke. Nilainya adalah isi dari objek itu, bukan pointer yang sebenarnya. Seperti dalam setiap kasus lainnya
&object2 != &*(new A())
,.Kami telah kehilangan pointer ke memori yang dialokasikan, dan karenanya kami tidak dapat membebaskannya.
delete &object2;
mungkin tampak seperti itu akan berhasil, tetapi karena&object2 != &*(new A())
, itu tidak setara dengandelete (new A())
dan tidak valid.sumber
Di C # dan Java, Anda menggunakan yang baru untuk membuat turunan dari kelas apa pun dan kemudian Anda tidak perlu khawatir tentang menghancurkannya nanti.
C ++ juga memiliki kata kunci "baru" yang membuat objek tetapi tidak seperti di Jawa atau C #, itu bukan satu-satunya cara untuk membuat objek.
C ++ memiliki dua mekanisme untuk membuat objek:
Dengan kreasi otomatis, Anda membuat objek dalam lingkungan lingkup: - dalam fungsi atau - sebagai anggota kelas (atau struct).
Dalam suatu fungsi Anda akan membuatnya dengan cara ini:
Dalam sebuah kelas Anda biasanya membuatnya seperti ini:
Dalam kasus pertama, objek dihancurkan secara otomatis ketika blok lingkup keluar. Ini bisa berupa fungsi atau blok ruang lingkup dalam suatu fungsi.
Dalam kasus yang terakhir objek b dihancurkan bersama dengan instance dari A di mana ia adalah anggota.
Objek dialokasikan dengan yang baru ketika Anda perlu mengontrol masa hidup objek dan kemudian membutuhkan penghapusan untuk menghancurkannya. Dengan teknik yang dikenal sebagai RAII, Anda menangani penghapusan objek pada titik yang Anda buat dengan meletakkannya di dalam objek otomatis, dan menunggu penghancur objek otomatis itu berlaku.
Salah satu objek tersebut adalah shared_ptr yang akan memanggil logika "deleter" tetapi hanya ketika semua instance shared_ptr yang berbagi objek dihancurkan.
Secara umum, sementara kode Anda mungkin memiliki banyak panggilan ke yang baru, Anda harus membatasi panggilan untuk dihapus dan harus selalu memastikan ini dipanggil dari destruktor atau "deleter" objek yang dimasukkan ke dalam smart-pointer.
Destruktor Anda juga seharusnya tidak pernah melemparkan pengecualian.
Jika Anda melakukan ini, Anda akan memiliki sedikit kebocoran memori.
sumber
automatic
dandynamic
. Ada jugastatic
.Baris ini adalah penyebab kebocoran. Mari kita selesaikan ini sedikit ..
object2 adalah variabel tipe B, disimpan di alamat say 1 (Ya, saya memilih angka arbitrer di sini). Di sisi kanan, Anda telah meminta B baru, atau pointer ke objek tipe B. Program dengan senang hati memberikan ini kepada Anda dan menetapkan B baru Anda ke alamat 2 dan juga membuat pointer di alamat 3. Sekarang, satu-satunya cara untuk mengakses data di alamat 2 adalah melalui pointer di alamat 3. Selanjutnya, Anda menentukan pointer menggunakan
*
untuk mendapatkan data yang menunjuk ke pointer (data di alamat 2). Ini secara efektif membuat salinan data itu dan memberikannya ke object2, ditugaskan di alamat 1. Ingat, ini COPY, bukan yang asli.Sekarang, inilah masalahnya:
Anda tidak pernah menyimpan pointer itu di mana pun Anda bisa menggunakannya! Setelah tugas ini selesai, pointer (memori di address3, yang Anda gunakan untuk mengakses address2) berada di luar jangkauan dan di luar jangkauan Anda! Anda tidak lagi dapat memanggil delete di atasnya dan karenanya tidak dapat membersihkan memori di address2. Yang tersisa adalah salinan data dari address2 di address1. Dua hal yang sama duduk dalam ingatan. Satu Anda dapat mengakses, yang lain Anda tidak dapat (karena Anda kehilangan jalan ke sana). Itu sebabnya ini adalah kebocoran memori.
Saya akan menyarankan datang dari latar belakang C # Anda bahwa Anda banyak membaca tentang bagaimana pointer dalam C ++ bekerja. Mereka adalah topik tingkat lanjut dan perlu waktu untuk dipahami, tetapi penggunaannya akan sangat berharga bagi Anda.
sumber
Jika itu membuatnya lebih mudah, anggap memori komputer seperti hotel dan program adalah pelanggan yang menyewa kamar ketika mereka membutuhkannya.
Cara hotel ini bekerja adalah Anda memesan kamar dan memberi tahu portir ketika Anda pergi.
Jika Anda memprogram memesan kamar dan pergi tanpa memberitahu porter, porter akan berpikir bahwa ruangan itu masih digunakan dan tidak akan membiarkan orang lain menggunakannya. Dalam hal ini ada kebocoran kamar.
Jika program Anda mengalokasikan memori dan tidak menghapusnya (itu hanya berhenti menggunakannya) maka komputer berpikir bahwa memori tersebut masih digunakan dan tidak akan membiarkan orang lain menggunakannya. Ini adalah kebocoran memori.
Ini bukan analogi yang tepat tetapi mungkin bisa membantu.
sumber
Saat membuat
object2
Anda sedang membuat salinan dari objek yang Anda buat dengan yang baru, tetapi Anda juga kehilangan pointer (tidak pernah ditugaskan) (jadi tidak ada cara untuk menghapusnya nanti). Untuk menghindari ini, Anda harus membuatobject2
referensi.sumber
Nah, Anda membuat kebocoran memori jika Anda tidak membebaskan memori yang telah Anda alokasikan menggunakan
new
operator dengan mengirimkan pointer ke memori tersebut kedelete
operator.Dalam dua kasus Anda di atas:
Di sini Anda tidak menggunakan
delete
untuk membebaskan memori, jadi jika dan ketikaobject1
pointer Anda keluar dari ruang lingkup, Anda akan memiliki kebocoran memori, karena Anda akan kehilangan pointer dan karenanya tidak dapat menggunakandelete
operator di dalamnya.Dan di sini
Anda membuang pointer yang dikembalikan oleh
new B()
, dan karenanya tidak pernah bisa melewatkan pointer itu agardelete
memori bisa dibebaskan. Oleh karena itu memori lain bocor.sumber
Baris inilah yang segera bocor:
Di sini Anda membuat
B
objek baru di tumpukan, lalu membuat salinan di tumpukan. Yang telah dialokasikan pada heap tidak lagi dapat diakses dan karenanya bocor.Baris ini tidak langsung bocor:
Akan ada kebocoran jika Anda tidak pernah
delete
dobject1
sekalipun.sumber