Jika saya perlu menggunakan sepotong memori sepanjang umur program saya, apakah benar-benar perlu untuk membebaskannya tepat sebelum penghentian program?

67

Dalam banyak buku dan tutorial, saya pernah mendengar praktik manajemen memori menekankan dan merasa bahwa beberapa hal misterius dan mengerikan akan terjadi jika saya tidak membebaskan memori setelah saya selesai menggunakannya.

Saya tidak dapat berbicara untuk sistem lain (walaupun bagi saya masuk akal untuk berasumsi bahwa mereka mengadopsi praktik yang sama), tetapi setidaknya pada Windows, Kernel pada dasarnya dijamin untuk membersihkan sebagian besar sumber daya (dengan pengecualian beberapa ganjil) yang digunakan oleh sebuah program setelah penghentian program. Yang termasuk memori tumpukan, antara berbagai hal lainnya.

Saya mengerti mengapa Anda ingin menutup file setelah selesai menggunakannya untuk membuatnya tersedia bagi pengguna atau mengapa Anda ingin memutuskan soket yang terhubung ke server untuk menghemat bandwidth, tetapi tampaknya konyol untuk harus mengatur SEMUA memori Anda yang digunakan oleh program Anda.

Sekarang, saya setuju bahwa pertanyaan ini luas karena bagaimana Anda harus menangani memori Anda didasarkan pada berapa banyak memori yang Anda butuhkan dan ketika Anda membutuhkannya, jadi saya akan mempersempit ruang lingkup pertanyaan ini menjadi ini: Jika saya perlu menggunakan sepotong memori sepanjang umur program saya, apakah benar-benar perlu untuk membebaskannya tepat sebelum penghentian program?

Sunting: Pertanyaan yang disarankan sebagai duplikat khusus untuk keluarga sistem operasi Unix. Jawaban teratasnya bahkan menentukan alat khusus untuk Linux (misalnya Valgrind). Pertanyaan ini dimaksudkan untuk mencakup sebagian besar sistem operasi non-embedded "normal" dan mengapa itu adalah praktik yang baik atau tidak untuk membebaskan memori yang diperlukan sepanjang umur suatu program.

Kapten Jelas
sumber
19
Anda telah menandai bahasa ini agnostik tetapi dalam banyak bahasa (mis. Java), memori sama sekali tidak diperlukan; itu terjadi secara otomatis beberapa saat setelah referensi terakhir ke suatu objek keluar dari ruang lingkup
Richard Tingle
3
dan tentu saja Anda dapat menulis C ++ tanpa menghapus tunggal dan itu akan baik-baik saja untuk memori
Nikko
5
@RichardTingle Meskipun saya tidak bisa memikirkan bahasa lain selain dari kepala saya selain C dan mungkin C ++, tag bahasa-agnostik dimaksudkan untuk mencakup semua bahasa yang tidak memiliki banyak utilitas pengumpulan sampah bawaan. Meskipun demikian, ini jarang terjadi. Anda bisa berargumen bahwa Anda sekarang dapat mengimplementasikan sistem seperti itu di C ++, tetapi Anda masih memiliki pilihan untuk tidak menghapus sepotong memori.
CaptainObvious
4
Anda menulis: "Kernel pada dasarnya dijamin untuk membersihkan semua sumber daya [...] setelah penghentian program". Ini umumnya salah, karena tidak semuanya adalah memori, pegangan, atau objek kernel. Tetapi itu benar untuk ingatan, di mana Anda segera membatasi pertanyaan Anda.
Eric Towers

Jawaban:

108

Jika saya perlu menggunakan sepotong memori sepanjang umur program saya, apakah benar-benar perlu untuk membebaskannya tepat sebelum penghentian program?

Ini tidak wajib, tetapi dapat memiliki manfaat (serta beberapa kelemahan).

Jika program mengalokasikan memori satu kali selama waktu pelaksanaannya, dan jika tidak akan pernah melepaskannya sampai proses berakhir, itu mungkin merupakan pendekatan yang masuk akal untuk tidak melepaskan memori secara manual dan bergantung pada OS. Pada setiap OS modern yang saya tahu, ini aman, pada akhir proses semua memori yang dialokasikan dikembalikan ke sistem dengan andal.
Dalam beberapa kasus, tidak membersihkan memori yang dialokasikan secara eksplisit bahkan mungkin lebih cepat daripada melakukan pembersihan.

Namun, dengan merilis semua memori pada akhir eksekusi secara eksplisit,

  • selama debugging / pengujian, alat deteksi kebocoran tidak akan menampilkan "false positive"
  • mungkin lebih mudah untuk memindahkan kode yang menggunakan memori bersama dengan alokasi dan deallokasi ke komponen yang terpisah dan menggunakannya nanti dalam konteks yang berbeda di mana waktu penggunaan untuk memori perlu dikontrol oleh pengguna komponen

Umur program dapat berubah. Mungkin program Anda adalah utilitas baris perintah kecil hari ini, dengan masa hidup khas kurang dari 10 menit, dan itu mengalokasikan memori dalam porsi beberapa kb setiap 10 detik - jadi tidak perlu membebaskan memori yang dialokasikan sama sekali sebelum program berakhir. Kemudian program diubah dan mendapatkan penggunaan yang diperpanjang sebagai bagian dari proses server dengan masa hidup beberapa minggu - jadi tidak membebaskan memori yang tidak terpakai di antara keduanya bukanlah pilihan lagi, jika tidak, program Anda mulai memakan semua memori server yang tersedia sepanjang waktu . Ini berarti Anda harus meninjau keseluruhan program dan menambahkan kode deallocating sesudahnya. Jika Anda beruntung, ini adalah tugas yang mudah, jika tidak, mungkin sangat sulit sehingga kemungkinan besar Anda kehilangan tempat. Dan ketika Anda berada dalam situasi itu, Anda akan berharap telah menambahkan "gratis"

Secara lebih umum, penulisan alokasi dan kode deallocating terkait selalu dianggap berpasangan sebagai "kebiasaan baik" di antara banyak programmer: dengan melakukan ini selalu, Anda mengurangi kemungkinan lupa kode deallokasi dalam situasi di mana memori harus dibebaskan.

Doc Brown
sumber
12
Poin bagus tentang mengembangkan kebiasaan pemrograman yang baik.
Lawrence
4
Secara umum saya sangat setuju dengan saran ini. Menjaga kebiasaan baik dengan ingatan sangat penting. Namun, saya pikir ada pengecualian. Misalnya, jika yang Anda alokasikan adalah beberapa grafik gila yang akan membutuhkan beberapa detik untuk dilalui dan membebaskan "dengan benar," maka Anda akan meningkatkan pengalaman pengguna dengan hanya mengakhiri program dan membiarkan OS membiarkannya.
GrandOpener
1
Ini aman pada setiap OS "normal" (non-tertanam dengan perlindungan memori) modern, dan akan menjadi bug serius jika tidak. Jika sebuah proses yang tidak terprivat dapat secara permanen kehilangan memori sehingga OS tidak akan mengklaim kembali, maka menjalankannya berkali-kali dapat menjalankan sistem dari kehabisan memori. Kesehatan jangka panjang OS tidak dapat bergantung pada kurangnya bug dalam program yang tidak terjangkau. (tentu saja, ini mengabaikan hal-hal seperti segmen memori bersama Unix, yang hanya didukung oleh ruang swap yang terbaik.)
Peter Cordes
7
@GrandOpener Dalam hal ini Anda mungkin lebih suka menggunakan semacam pengalokasi berbasis wilayah untuk pohon ini, sehingga Anda dapat mengalokasikannya dengan cara normal dan hanya membatalkan alokasi seluruh wilayah sekaligus ketika saatnya tiba, alih-alih melintasinya dan membebaskannya. sedikit demi sedikit. Itu masih "layak".
Thomas
3
Selain itu, jika memori benar-benar perlu ada untuk seumur hidup program, itu mungkin merupakan alternatif yang masuk akal untuk membuat struktur pada stack, misalnya di main.
Kyle Strand
11

Mengosongkan memori di akhir program yang dijalankan hanya membuang waktu CPU. Ini seperti merapikan rumah sebelum melepaskannya dari orbit.

Namun terkadang program yang tadinya singkat dapat berubah menjadi bagian dari program yang lebih lama berjalan. Maka hal-hal yang membebaskan menjadi perlu. Jika ini tidak dipikirkan setidaknya sampai batas tertentu maka itu bisa melibatkan pengerjaan ulang substansial.

Salah satu solusi cerdas untuk ini adalah "talloc" yang memungkinkan Anda membuat alokasi memori yang banyak dan kemudian membuang semuanya dengan satu panggilan.

Peter Green
sumber
1
Dengan asumsi Anda tahu OS Anda tidak memiliki kebocoran sendiri.
WGroleau
5
"buang-buang waktu CPU" Meskipun jumlah waktu yang sangat, sangat kecil. freebiasanya jauh lebih cepat daripada malloc.
Paul Draper
@ PaulDraper Aye, buang-buang waktu menukar semuanya kembali jauh lebih signifikan.
Deduplicator
5

Anda dapat menggunakan bahasa dengan pengumpulan sampah (seperti Skema, Ocaml, Haskell, Common Lisp, dan bahkan Java, Scala, Clojure).

(Dalam kebanyakan bahasa GC-ed, tidak ada cara untuk membebaskan memori secara eksplisit dan manual ! Kadang-kadang, beberapa nilai mungkin diselesaikan , misalnya GC dan sistem runtime akan menutup nilai pegangan file ketika nilai itu tidak dapat dijangkau; tetapi ini tidak yakin, tidak dapat diandalkan, dan Anda harus menutup secara eksplisit file Anda, karena finalisasi tidak pernah dijamin)

Anda juga bisa, untuk program Anda yang dikodekan dalam C (atau bahkan C ++) menggunakan pengumpul sampah Boehm yang konservatif . Anda kemudian akan mengganti semua mallocdengan GC_malloc dan tidak repot-repot freedengan pointer apa pun. Tentu saja Anda perlu memahami pro dan conses penggunaan Boehm's GC. Baca juga buku pegangan GC .

Manajemen memori adalah properti global dari suatu program. Dalam beberapa cara itu (dan liveness dari beberapa data yang diberikan) adalah non-komposisi dan non-modular, karena seluruh properti program.

Akhirnya, seperti yang ditunjukkan orang lain, secara eksplisit free-menggunakan heap yang dialokasikan zona memori C Anda adalah praktik yang baik. Untuk program mainan yang tidak mengalokasikan banyak memori, Anda bahkan dapat memutuskan untuk tidak freemengingat sama sekali (karena ketika prosesnya telah berakhir, sumber dayanya, termasuk ruang alamat virtualnya , akan dibebaskan oleh sistem operasi).

Jika saya perlu menggunakan sepotong memori sepanjang umur program saya, apakah benar-benar perlu untuk membebaskannya tepat sebelum penghentian program?

Tidak, kamu tidak perlu melakukan itu. Dan banyak program dunia nyata tidak repot-repot membebaskan beberapa memori yang diperlukan pada seluruh umur (khususnya kompiler GCC tidak membebaskan sebagian dari memorinya). Namun, ketika Anda melakukan itu (mis. Anda tidak repot-repot mengambil freebagian tertentu dari data yang dialokasikan secara dinamis C ), Anda akan lebih baik berkomentar fakta itu untuk memudahkan pekerjaan programmer masa depan pada proyek yang sama. Saya cenderung merekomendasikan bahwa jumlah memori yang tidak tetap tetap dibatasi, dan biasanya wrt yang relatif kecil untuk total memori tumpukan yang digunakan.

Perhatikan bahwa sistem freesering tidak melepaskan memori ke OS (misalnya dengan memanggil munmap (2) pada sistem POSIX) tetapi biasanya menandai zona memori sebagai dapat digunakan kembali di masa depan malloc. Secara khusus, ruang alamat virtual (mis. Seperti yang terlihat /proc/self/mapspada Linux, lihat proc (5) ....) mungkin tidak menyusut setelahnya free(karena itu utilitas menyukai psatau topmelaporkan jumlah memori yang digunakan untuk proses Anda).

Basile Starynkevitch
sumber
3
"Kadang-kadang, beberapa nilai mungkin diselesaikan, misalnya GC dan sistem runtime akan menutup nilai menangani file ketika nilai itu tidak terjangkau" Anda mungkin ingin menekankan bahwa dalam sebagian besar bahasa GCed tidak ada jaminan memori apa pun akan pernah direklamasi, atau penyelesai apa pun yang pernah dijalankan, bahkan saat keluar: stackoverflow.com/questions/7880569/…
Deduplicator
Saya pribadi lebih suka jawaban ini, karena mendorong kita menggunakan GC (yaitu pendekatan yang lebih otomatis).
Ta Thanh Dinh
3
@tathanhdinh Lebih otomatis, meskipun khusus untuk memori. Manual sepenuhnya, untuk semua sumber daya lainnya. GC bekerja dengan determinisme perdagangan dan memori untuk penanganan memori yang nyaman. Dan tidak, finalis tidak banyak membantu, dan memiliki masalah sendiri.
Deduplicator
3

Tidak perlu , karena Anda tidak akan gagal menjalankan program dengan benar jika gagal. Namun, ada beberapa alasan yang bisa Anda pilih, jika diberi kesempatan.

Salah satu kasus paling kuat yang saya temui (berulang kali) adalah seseorang menulis sepotong kecil kode simulasi yang berjalan di executable mereka. Mereka mengatakan "kami ingin kode ini diintegrasikan ke dalam simulasi." Saya kemudian bertanya kepada mereka bagaimana mereka berencana untuk menginisialisasi ulang di antara monte-carlo run, dan mereka menatap saya dengan kosong. "Apa maksudmu menginisialisasi ulang? Kamu baru saja menjalankan program dengan pengaturan baru?"

Terkadang pembersihan bersih membuat perangkat lunak Anda lebih mudah digunakan. Dalam banyak contoh, Anda menganggap Anda tidak perlu membersihkan sesuatu, dan Anda membuat asumsi tentang bagaimana Anda bisa menangani data dan masa pakainya di sekitar asumsi tersebut. Saat Anda pindah ke lingkungan baru di mana anggapan tersebut tidak valid, seluruh algoritma mungkin tidak lagi berfungsi.

Untuk contoh betapa anehnya hal-hal aneh, lihat bagaimana bahasa yang dikelola menangani finalisasi di akhir proses, atau bagaimana C # menangani penghentian terprogram dari Aplikasi Domain. Mereka mengikat diri mereka dalam simpul karena ada asumsi yang jatuh melalui celah-celah dalam kasus ekstrim ini.

Cort Ammon
sumber
1

Mengabaikan bahasa tempat Anda tidak mengosongkan memori secara manual ...

Apa yang Anda anggap sebagai "sebuah program" saat ini mungkin pada beberapa titik hanya menjadi fungsi atau metode yang merupakan bagian dari program yang lebih besar. Dan kemudian fungsi itu dipanggil beberapa kali. Dan kemudian memori yang seharusnya Anda "bebaskan secara manual" akan menjadi kebocoran memori. Itu tentu saja panggilan penghakiman.

gnasher729
sumber
2
ini sepertinya hanya mengulangi poin yang dibuat (dan dijelaskan dengan jauh lebih baik) dalam jawaban teratas yang diposting beberapa jam sebelumnya: "mungkin lebih mudah untuk memindahkan kode yang menggunakan memori bersama dengan alokasi dan deallokasi ke komponen yang terpisah dan menggunakannya nanti dalam konteks yang berbeda di mana waktu penggunaan untuk memori perlu dikontrol oleh pengguna komponen ... "
agas
1

Karena sangat mungkin bahwa dalam beberapa waktu Anda ingin mengubah program Anda, mungkin mengintegrasikannya dengan sesuatu yang lain, jalankan beberapa contoh secara berurutan atau paralel. Maka akan diperlukan untuk membebaskan memori ini secara manual - tetapi Anda tidak akan mengingat keadaan lagi dan itu akan menghabiskan lebih banyak waktu untuk memahami kembali program Anda.

Lakukan hal-hal sementara pemahaman Anda tentang mereka masih segar.

Ini adalah investasi kecil yang dapat menghasilkan pengembalian besar di masa depan.

Agent_L
sumber
2
ini sepertinya tidak menambah sesuatu yang substansial pada poin yang dibuat dan dijelaskan dalam 11 jawaban sebelumnya. Secara khusus, poin tentang kemungkinan mengubah program di masa depan telah dibuat tiga atau empat kali
agas
1
@gnat Dengan cara yang berbelit-belit dan tidak jelas - ya. Dalam pernyataan yang jelas - tidak. Mari kita fokus pada kualitas jawaban, bukan kecepatan.
Agent_L
1
Dari segi kualitas, jawaban teratas tampaknya jauh lebih baik dalam menjelaskan hal ini
agas
1

Tidak, itu tidak perlu, tapi itu ide yang bagus.

Anda mengatakan Anda merasa bahwa "hal-hal misterius dan mengerikan akan terjadi jika saya tidak membebaskan memori setelah saya selesai menggunakannya."

Dalam istilah teknis, satu-satunya konsekuensi dari ini adalah bahwa program Anda akan terus memakan lebih banyak memori hingga batas yang sulit tercapai (mis. Ruang alamat virtual Anda habis) atau kinerja menjadi tidak dapat diterima. Jika program akan keluar, tidak ada yang penting karena prosesnya secara efektif tidak ada lagi. "Hal-hal misterius dan mengerikan" adalah murni tentang kondisi mental pengembang. Menemukan sumber kebocoran memori bisa menjadi mimpi buruk absolut (ini meremehkan) dan dibutuhkan banyak keterampilan dan disiplin untuk menulis kode yang bebas kebocoran. Pendekatan yang disarankan untuk mengembangkan keterampilan dan disiplin ini adalah untuk selalu membebaskan memori setelah tidak diperlukan lagi, bahkan jika program akan segera berakhir.

Tentu saja ini memiliki keuntungan tambahan bahwa kode Anda dapat digunakan kembali dan diadaptasi dengan lebih mudah, seperti yang dikatakan orang lain.

Namun , setidaknya ada satu kasus di mana lebih baik untuk tidak membebaskan memori sebelum penghentian program.

Pertimbangkan kasus di mana Anda telah membuat jutaan alokasi kecil, dan sebagian besar telah ditukar ke disk. Ketika Anda mulai membebaskan semuanya, sebagian besar memori Anda perlu ditukar kembali ke RAM sehingga informasi pembukuan dapat diakses, hanya untuk segera membuang data. Ini bisa membuat program butuh beberapa menit hanya untuk keluar! Belum lagi memberi banyak tekanan pada disk dan memori fisik selama waktu itu. Jika memori fisik tidak mencukupi untuk memulai (mungkin program sedang ditutup karena program lain mengunyah banyak memori) maka halaman individual mungkin perlu ditukar masuk dan keluar beberapa kali ketika beberapa objek perlu dibebaskan dari halaman yang sama, tetapi tidak dibebaskan secara berurutan.

Jika bukan program yang hanya batal, OS hanya akan membuang semua memori yang telah ditukar ke disk, yang hampir seketika karena tidak memerlukan akses disk sama sekali.

Penting untuk dicatat bahwa dalam bahasa OO, memanggil destruktor objek juga akan memaksa memori untuk ditukar; jika Anda harus melakukan ini, Anda bisa membebaskan memori.

Artelius
sumber
1
Bisakah Anda mengutip sesuatu untuk mendukung gagasan bahwa memori paged-out perlu di-paged untuk deallocate? Itu jelas tidak efisien, pasti OS modern lebih pintar dari itu!
1
@Jon dari Semua Perdagangan, OS modern cerdas, tetapi ironisnya sebagian besar bahasa modern tidak. Saat Anda membebaskan (sesuatu), kompiler akan meletakkan "sesuatu" di tumpukan, kemudian memanggil bebas (). Menempatkannya di tumpukan membutuhkan menukar kembali ke RAM jika itu ditukar.
Jeffiekins
@JonofAllTrades daftar bebas akan memiliki efek itu, itu implementasi malloc yang cukup usang sekalipun. Tetapi OS tidak dapat mencegah Anda menggunakan implementasi semacam itu.
0

Alasan utama untuk membersihkan secara manual adalah: Anda cenderung tidak akan kesalahan kebocoran memori asli untuk blok memori yang hanya akan dibersihkan saat keluar, Anda kadang-kadang akan menangkap bug di pengalokasi hanya ketika Anda berjalan seluruh struktur data untuk deallocate, dan Anda akan memiliki sakit kepala yang jauh lebih kecil jika Anda pernah refactor sehingga beberapa objek tidak perlu di-deallocated sebelum program keluar.

Alasan utama untuk tidak membersihkan secara manual adalah: kinerja, kinerja, kinerja, dan kemungkinan beberapa jenis bug bebas-dua kali atau bebas-pakai dalam kode pembersihan yang tidak perlu, yang dapat berubah menjadi crash bug atau keamanan mengeksploitasi.

Jika Anda peduli dengan kinerja, Anda selalu ingin profil untuk mencari tahu di mana Anda membuang-buang waktu, alih-alih menebak. Kemungkinan kompromi adalah dengan membungkus kode pembersihan opsional Anda dalam blok bersyarat, biarkan ketika debugging sehingga Anda mendapatkan manfaat dari penulisan dan debugging kode, dan kemudian, jika dan hanya jika Anda telah menentukan secara empiris bahwa itu terlalu banyak overhead, beri tahu kompiler untuk melewatinya di executable akhir Anda. Dan penundaan dalam menutup program, hampir secara definisi, hampir tidak pernah di jalur kritis.

Davislor
sumber