Java memiliki GC otomatis yang sesekali Menghentikan Dunia, tetapi mengurus sampah di tumpukan. Sekarang aplikasi C / C ++ tidak memiliki pembekuan STW ini, penggunaan memori mereka juga tidak bertambah. Bagaimana perilaku ini tercapai? Bagaimana benda mati dirawat?
c++
c
garbage-collection
Ju Shua
sumber
sumber
new
.What happens to garbage in C++?
Bukankah biasanya dikompilasi menjadi executable?Jawaban:
Programmer bertanggung jawab untuk memastikan bahwa objek yang mereka buat
new
dihapus melaluidelete
. Jika suatu objek dibuat, tetapi tidak dihancurkan sebelum pointer terakhir atau referensi untuk itu keluar dari ruang lingkup, ia jatuh melalui celah-celah dan menjadi Memory Leak .Sayangnya untuk C, C ++ dan bahasa lain yang tidak termasuk GC, ini hanya menumpuk dari waktu ke waktu. Ini dapat menyebabkan aplikasi atau sistem kehabisan memori dan tidak dapat mengalokasikan blok memori baru. Pada titik ini, pengguna harus menggunakan untuk mengakhiri aplikasi sehingga Sistem Operasi dapat merebut kembali memori yang digunakan.
Sejauh mengatasi masalah ini, ada beberapa hal yang membuat hidup seorang programmer jauh lebih mudah. Ini terutama didukung oleh sifat ruang lingkup .
Di sini, kami membuat dua variabel. Mereka ada di Blok Lingkup , seperti yang didefinisikan oleh
{}
kurung kurawal. Ketika eksekusi bergerak keluar dari lingkup ini, objek-objek ini akan dihapus secara otomatis. Dalam hal ini,variableThatIsAPointer
seperti namanya, adalah penunjuk ke objek di memori. Ketika keluar dari ruang lingkup, pointer dihapus, tetapi objek yang ditunjuknya tetap ada. Di sini, kitadelete
objek ini sebelum keluar dari ruang lingkup untuk memastikan bahwa tidak ada kebocoran memori. Namun kami juga bisa melewati pointer ini di tempat lain dan berharap itu akan dihapus nanti.Sifat lingkup ini meluas ke kelas:
Di sini, prinsip yang sama berlaku. Kami tidak perlu khawatir tentang
bar
kapanFoo
dihapus. Namun untukotherBar
, hanya pointer yang dihapus. JikaotherBar
satu-satunya penunjuk yang valid untuk objek apa pun yang ditunjukkannya, kita mungkin harusdelete
dalamFoo
destruktor. Ini adalah konsep pendorong di belakang RAIIRAII juga merupakan kekuatan pendorong khas di belakang Smart Pointers . Dalam Standard Library C ++, ini
std::shared_ptr
,std::unique_ptr
danstd::weak_ptr
; walaupun saya telah melihat dan menggunakanshared_ptr
/weak_ptr
implementasi lain yang mengikuti konsep yang sama. Untuk ini, penghitung referensi melacak berapa banyak pointer ke objek yang diberikan, dan secara otomatisdelete
objek setelah tidak ada lagi referensi untuk itu.Lebih dari itu, semuanya bermuara pada praktik dan disiplin yang tepat bagi seorang programmer untuk memastikan bahwa kode mereka menangani objek dengan benar.
sumber
delete
- itulah yang saya cari. Luar biasa.delete
otomatis dipanggil untuk Anda oleh pointer pintar jika Anda menggunakannya sehingga Anda harus mempertimbangkan menggunakannya setiap kali ketika penyimpanan otomatis tidak dapat digunakan.delete
kode aplikasi Anda (dan mulai C ++ 14 dan seterusnya, sama dengannew
), tetapi gunakan pointer pintar dan RAII agar objek tumpukan dihapus.std::unique_ptr
jenis danstd::make_unique
fungsi adalah langsung, penggantian sederhananew
dandelete
pada tingkat kode aplikasi.C ++ tidak memiliki pengumpulan sampah.
Aplikasi C ++ diperlukan untuk membuang sampah mereka sendiri.
Pemrogram aplikasi C ++ harus memahami hal ini.
Ketika mereka lupa, hasilnya disebut "kebocoran memori".
sumber
new
dandelete
.malloc
danfree
, ataunew[]
dandelete[]
, atau penyalur lainnya (seperti Windows iniGlobalAlloc
,LocalAlloc
,SHAlloc
,CoTaskMemAlloc
,VirtualAlloc
,HeapAlloc
, ...), dan memori yang dialokasikan untuk Anda (misalnya melaluifopen
).Dalam C, C ++ dan sistem lain tanpa Pengumpul Sampah, pengembang ditawarkan fasilitas oleh bahasa dan pustaka untuk menunjukkan kapan memori dapat direklamasi.
Fasilitas paling mendasar adalah penyimpanan otomatis . Sering kali, bahasa itu sendiri memastikan bahwa barang-barang dibuang:
Dalam kasus ini, kompiler bertugas mengetahui kapan nilai-nilai itu tidak digunakan dan mengambil kembali penyimpanan yang terkait dengannya.
Saat menggunakan penyimpanan dinamis , dalam C, memori secara tradisional dialokasikan dengan
malloc
dan direklamasi denganfree
. Dalam C ++, memori secara tradisional dialokasikan dengannew
dan direklamasi dengandelete
.C tidak banyak berubah selama bertahun-tahun, namun C ++ modern menghindar
new
dandelete
sepenuhnya bergantung pada fasilitas perpustakaan (yang digunakannew
dan digunakan dengandelete
tepat):std::unique_ptr
danstd::shared_ptr
std::string
,std::vector
,std::map
, ... semua internal mengelola memori dialokasikan secara dinamis transparanOmong-omong
shared_ptr
, ada risiko: jika siklus referensi terbentuk, dan tidak rusak, maka kebocoran memori mungkin ada. Terserah pengembang untuk menghindari situasi ini, cara paling sederhana untuk menghindarishared_ptr
sama sekali dan yang paling sederhana adalah untuk menghindari siklus di tingkat tipe.Akibatnya kebocoran memori tidak menjadi masalah dalam C ++ , bahkan untuk pengguna baru, selama mereka tidak menggunakan
new
,delete
ataustd::shared_ptr
. Ini tidak seperti C di mana disiplin yang kuat diperlukan, dan umumnya tidak memadai.Namun, jawaban ini tidak akan lengkap tanpa menyebutkan saudara kembar kebocoran memori: pointer menggantung .
Pointer menggantung (atau referensi menggantung) adalah bahaya yang dibuat dengan menjaga pointer atau referensi ke objek yang mati. Sebagai contoh:
Menggunakan pointer yang menggantung, atau referensi, adalah Perilaku Tidak Terdefinisi . Secara umum, untungnya, ini adalah crash langsung; cukup sering, sayangnya, ini menyebabkan kerusakan memori pertama ... dan dari waktu ke waktu perilaku aneh muncul karena kompiler memancarkan kode yang sangat aneh.
Perilaku tidak terdefinisi adalah masalah terbesar dengan C dan C ++ hingga hari ini, dalam hal keamanan / kebenaran program. Anda mungkin ingin melihat bahasa Rust tanpa Pengumpul Sampah dan Perilaku Tidak Terdefinisi.
sumber
new
,delete
danshared_ptr
"; tanpanew
danshared_ptr
Anda memiliki kepemilikan langsung sehingga tidak ada kebocoran. Tentu saja, Anda cenderung memiliki pointer menggantung, dll ... tapi saya khawatir Anda harus meninggalkan C ++ untuk menyingkirkannya.C ++ memiliki hal ini disebut RAII . Pada dasarnya itu berarti sampah dibersihkan saat Anda pergi daripada meninggalkannya di tumpukan dan membiarkan pembersih merapikan setelah Anda. (bayangkan saya di kamar saya menonton sepak bola - ketika saya minum kaleng bir dan membutuhkan yang baru, cara C ++ adalah dengan membawa kaleng kosong ke tempat sampah dalam perjalanan ke lemari es, cara C # adalah membuangnya ke lantai dan menunggu pelayan untuk mengambilnya ketika dia datang untuk membersihkan).
Sekarang dimungkinkan untuk membocorkan memori dalam C ++, tetapi untuk melakukannya mengharuskan Anda meninggalkan konstruksi yang biasa dan kembali ke cara C melakukan sesuatu - mengalokasikan blok memori dan melacak di mana blok itu tanpa bantuan bahasa. Beberapa orang lupa pointer ini sehingga tidak dapat menghapus blokir.
sumber
Perlu dicatat bahwa, dalam kasus C ++, kesalahpahaman umum bahwa "Anda perlu melakukan manajemen memori manual". Bahkan, Anda biasanya tidak melakukan manajemen memori dalam kode Anda.
Objek ukuran tetap (dengan masa hidup lingkup)
Dalam sebagian besar kasus ketika Anda membutuhkan objek, objek tersebut akan memiliki masa hidup yang ditentukan dalam program Anda dan dibuat di stack. Ini berfungsi untuk semua tipe data primitif bawaan, tetapi juga untuk instance kelas dan struct:
Tumpukan objek secara otomatis dihapus ketika fungsi berakhir. Di Jawa, objek selalu dibuat di heap, dan karena itu harus dihapus oleh beberapa mekanisme seperti pengumpulan sampah. Ini bukan masalah untuk objek tumpukan.
Objek yang mengelola data dinamis (dengan cakupan masa pakai)
Menggunakan ruang pada tumpukan berfungsi untuk objek dengan ukuran tetap. Ketika Anda membutuhkan jumlah ruang variabel, seperti array, pendekatan lain digunakan: Daftar ini diringkas dalam objek ukuran tetap yang mengelola memori dinamis untuk Anda. Ini berfungsi karena objek dapat memiliki fungsi pembersihan khusus, penghancur. Dijamin akan dipanggil ketika objek keluar dari ruang lingkup dan melakukan kebalikan dari konstruktor:
Tidak ada manajemen memori sama sekali dalam kode di mana memori digunakan. Satu-satunya hal yang perlu kita pastikan adalah bahwa objek yang kita tulis memiliki destruktor yang sesuai. Tidak peduli bagaimana kita meninggalkan ruang lingkup
listTest
, baik itu melalui pengecualian atau hanya dengan kembali dari itu, destruktor~MyList()
akan dipanggil dan kita tidak perlu mengelola memori apa pun.(Saya pikir ini adalah keputusan desain yang lucu untuk menggunakan operator biner BUKAN
~
,, untuk menunjukkan destruktor. Ketika digunakan pada angka, itu membalikkan bit; dalam analogi, ini menunjukkan bahwa apa yang dilakukan konstruktor terbalik.)Pada dasarnya semua objek C ++ yang membutuhkan memori dinamis menggunakan enkapsulasi ini. Itu telah disebut RAII ("akuisisi sumber daya adalah inisialisasi"), yang merupakan cara yang cukup aneh untuk mengekspresikan ide sederhana bahwa objek peduli dengan konten mereka sendiri; apa yang mereka peroleh adalah milik mereka untuk dibersihkan.
Objek polimorfik dan masa hidup di luar ruang lingkup
Sekarang, kedua kasus ini untuk memori yang memiliki masa pakai yang jelas: Masa pakai sama dengan ruang lingkup. Jika kita tidak ingin objek kedaluwarsa saat kita meninggalkan ruang lingkup, ada mekanisme ketiga yang dapat mengatur memori untuk kita: pointer cerdas. Pointer pintar juga digunakan ketika Anda memiliki instance objek yang tipenya bervariasi saat runtime, tetapi yang memiliki antarmuka umum atau kelas dasar:
Ada jenis lain dari smart pointer
std::shared_ptr
,, untuk berbagi objek di antara beberapa klien. Mereka hanya menghapus objek yang terkandung ketika klien terakhir keluar dari ruang lingkup, sehingga mereka dapat digunakan dalam situasi di mana sama sekali tidak diketahui berapa banyak klien akan ada dan berapa lama mereka akan menggunakan objek.Singkatnya, kami melihat bahwa Anda tidak benar-benar melakukan manajemen memori manual. Semuanya dienkapsulasi dan kemudian dirawat dengan cara yang sepenuhnya otomatis, manajemen memori berbasis ruang lingkup. Dalam kasus di mana ini tidak cukup, pointer pintar digunakan yang merangkum memori mentah.
Ini dianggap praktik yang sangat buruk untuk menggunakan pointer mentah sebagai pemilik sumber daya di mana saja dalam kode C ++, alokasi mentah di luar konstruktor, dan
delete
panggilan mentah di luar destruktor, karena mereka hampir mustahil untuk dikelola ketika pengecualian terjadi, dan umumnya sulit digunakan dengan aman.Yang terbaik: ini bekerja untuk semua jenis sumber daya
Salah satu manfaat terbesar RAII adalah tidak terbatas pada memori. Ini sebenarnya menyediakan cara yang sangat alami untuk mengelola sumber daya seperti file dan soket (membuka / menutup) dan mekanisme sinkronisasi seperti mutex (mengunci / membuka kunci). Pada dasarnya, setiap sumber daya yang dapat diperoleh dan harus dirilis dikelola dengan cara yang persis sama di C ++, dan tidak satu pun dari manajemen ini diserahkan kepada pengguna. Itu semua dirangkum dalam kelas yang memperoleh di konstruktor dan rilis di destruktor.
Misalnya, fungsi mengunci mutex biasanya ditulis seperti ini di C ++:
Bahasa lain membuat ini jauh lebih rumit, baik dengan mengharuskan Anda melakukan ini secara manual (misalnya dalam
finally
klausa) atau mereka menelurkan mekanisme khusus yang memecahkan masalah ini, tetapi tidak dengan cara yang sangat elegan (biasanya nanti dalam hidup mereka, ketika cukup banyak orang memiliki menderita karena kekurangannya). Mekanisme seperti ini adalah coba-dengan-sumber daya di Jawa dan pernyataan penggunaan dalam C #, yang keduanya merupakan perkiraan RAII C ++.Jadi, singkatnya, semua ini adalah akun RAII yang sangat dangkal di C ++, tapi saya harap ini membantu pembaca untuk memahami bahwa memori dan bahkan manajemen sumber daya di C ++ biasanya tidak "manual", tetapi sebenarnya kebanyakan otomatis.
sumber
delete
dan Anda mati" meroket di atas 30 poin dan diterima, sementara yang ini memiliki lima. Adakah yang benar-benar menggunakan C ++ di sini?Sehubungan dengan C secara khusus, bahasa tidak memberi Anda alat untuk mengelola memori yang dialokasikan secara dinamis. Anda benar-benar bertanggung jawab untuk memastikan setiap orang
*alloc
memiliki tempat yang sesuaifree
.Di mana segala sesuatu menjadi benar-benar buruk adalah ketika alokasi sumber daya gagal di tengah jalan; apakah Anda mencoba lagi, apakah Anda memutar kembali dan memulai dari awal, apakah Anda memutar kembali dan keluar dengan kesalahan, apakah Anda langsung keluar jaminan dan membiarkan OS berurusan dengan itu?
Sebagai contoh, inilah fungsi untuk mengalokasikan array 2D yang tidak berdekatan. Perilaku di sini adalah bahwa jika kegagalan alokasi terjadi di tengah proses, kami memutar semuanya kembali dan mengembalikan indikasi kesalahan menggunakan pointer NULL:
Kode ini sangat jelek dengan
goto
itu, tetapi, jika tidak ada mekanisme penanganan pengecualian terstruktur, ini cukup banyak satu-satunya cara untuk mengatasi masalah tanpa hanya menyerah sepenuhnya, terutama jika kode alokasi sumber daya Anda bersarang lebih banyak lebih dari satu loop. Ini adalah salah satu dari beberapa kali dimanagoto
sebenarnya merupakan pilihan yang menarik; kalau tidak, Anda menggunakan banyak bendera danif
pernyataan tambahan .Anda dapat membuat hidup Anda lebih mudah dengan menulis fungsi pengalokasi / deallocator khusus untuk setiap sumber daya, misalnya
sumber
goto
pernyataan. Ini adalah praktik yang disarankan di beberapa daerah. Ini adalah skema yang biasa digunakan untuk melindungi terhadap pengecualian di C. Lihat kode kernel Linux, yang penuh dengangoto
pernyataan - dan yang tidak bocor.goto
adalah asing. Akan lebih mudah dibaca jika Anda mengubahgoto done;
kereturn arr;
danarr=NULL;done:return arr;
kereturn NULL;
. Meskipun dalam kasus-kasus yang lebih rumit mungkin memang ada banyakgoto
s, mulai membuka gulungan pada tingkat kesiapan yang berbeda (apa yang akan dilakukan oleh stack stack unwinding dalam C ++).Saya telah belajar untuk mengklasifikasikan masalah memori ke dalam sejumlah kategori berbeda.
Satu kali menetes. Misalkan sebuah program bocor 100 byte pada saat startup, hanya saja tidak pernah bocor lagi. Mengejar dan menghilangkan kebocoran satu kali itu bagus (saya suka memiliki laporan bersih dengan kemampuan deteksi kebocoran) tetapi tidak penting. Terkadang ada masalah yang lebih besar yang perlu diserang.
Kebocoran berulang. Fungsi yang disebut berulang-ulang selama masa hidup program yang secara teratur membocorkan memori merupakan masalah besar. Tetes ini akan menyiksa program, dan mungkin OS, sampai mati.
Referensi bersama. Jika objek A dan B merujuk satu sama lain melalui pointer bersama, Anda harus melakukan sesuatu yang istimewa, baik dalam desain kelas-kelas tersebut atau dalam kode yang mengimplementasikan / menggunakan kelas-kelas tersebut untuk memutus sirkularitas. (Ini bukan masalah untuk bahasa sampah yang dikumpulkan.)
Mengingat terlalu banyak. Ini adalah sepupu jahat sampah / memori yang bocor. RAII tidak akan membantu di sini, juga tidak akan mengumpulkan sampah. Ini masalah dalam bahasa apa pun. Jika beberapa variabel aktif memiliki jalur yang menghubungkannya ke beberapa memori acak, memori acak tersebut bukanlah sampah. Membuat program menjadi pelupa sehingga bisa berjalan selama beberapa hari itu sulit. Membuat program yang dapat berjalan selama beberapa bulan (misalnya, sampai disk gagal) sangat, sangat rumit.
Saya tidak punya masalah serius dengan kebocoran untuk waktu yang sangat lama. Menggunakan RAII di C ++ sangat membantu mengatasi tetesan dan kebocoran itu. (Namun seseorang harus berhati-hati dengan pointer bersama.) Jauh lebih penting saya punya masalah dengan aplikasi yang penggunaan ingatannya terus tumbuh dan tumbuh dan berkembang karena koneksi yang tidak rata ke memori yang tidak lagi digunakan.
sumber
Terserah kepada programmer C ++ untuk mengimplementasikan bentuk pengumpulan sampahnya sendiri jika diperlukan. Kegagalan untuk melakukannya akan menghasilkan apa yang disebut 'kebocoran memori'. Sangat umum untuk bahasa 'tingkat tinggi' (seperti Java) dibangun dalam pengumpulan sampah, tetapi bahasa 'tingkat rendah' seperti C dan C ++ tidak.
sumber