Dalam bahasa seperti C, programmer diharapkan memasukkan panggilan gratis. Mengapa kompiler tidak melakukan ini secara otomatis? Manusia melakukannya dalam jumlah waktu yang wajar (mengabaikan serangga), jadi itu tidak mustahil.
EDIT: Untuk referensi di masa mendatang, berikut adalah diskusi lain yang memiliki contoh menarik.
compilers
memory-management
garbage-collection
Milton Silva
sumber
sumber
Jawaban:
Karena tidak diputuskan apakah program akan menggunakan memori lagi. Ini berarti bahwa tidak ada algoritma yang dapat menentukan dengan benar kapan harus memanggil
free()
semua kasus, yang berarti bahwa setiap kompiler yang mencoba melakukan hal ini tentu akan menghasilkan beberapa program dengan kebocoran memori dan / atau beberapa program yang terus menggunakan memori yang telah dibebaskan. Bahkan jika Anda memastikan bahwa kompiler Anda tidak pernah melakukan yang kedua dan memungkinkan programmer untuk memasukkan panggilan untukfree()
memperbaiki bug tersebut, mengetahui kapan harus memanggilfree()
kompiler itu akan lebih sulit daripada mengetahui kapan harus meneleponfree()
saat menggunakan kompiler yang tidak mencoba untuk membantu.sumber
free()
dengan benar.Seperti yang dikatakan David Richerby dengan benar, masalahnya secara umum tidak dapat dipastikan. Keberanian objek adalah properti global dari program, dan mungkin secara umum bergantung pada input ke program.
Bahkan pengumpulan sampah dinamis yang presisi adalah masalah yang tidak dapat dipastikan! Semua pengumpul sampah dunia nyata menggunakan keterjangkauan sebagai perkiraan konservatif apakah objek yang dialokasikan akan dibutuhkan di masa depan. Ini adalah pendekatan yang baik, tetapi ini adalah perkiraan.
Tapi itu hanya berlaku secara umum. Salah satu solusi paling terkenal dalam bisnis ilmu komputer adalah "secara umum tidak mungkin, oleh karena itu kita tidak dapat melakukan apa-apa". Sebaliknya, ada banyak kasus di mana dimungkinkan untuk membuat kemajuan.
Implementasi berdasarkan penghitungan referensi sangat dekat dengan "kompilasi penempatan deallocations" sehingga sulit untuk membedakannya. LLVM 's penghitungan referensi otomatis (digunakan dalam Objective-C dan Swift ) adalah contoh yang terkenal.
Inferensi wilayah dan pengumpulan sampah waktu adalah area penelitian aktif saat ini. Ternyata jauh lebih mudah dalam bahasa deklaratif seperti ML dan Merkurius , di mana Anda tidak dapat memodifikasi objek setelah itu dibuat.
Sekarang, pada topik manusia, ada tiga cara utama manusia mengelola alokasi hidup secara manual:
sumber
Ini masalah ketidaklengkapan, bukan masalah ketidakpastian
Meskipun benar bahwa penempatan optimal dari pernyataan deallokasi tidak dapat diputuskan, itu bukan masalah di sini. Karena tidak dapat ditentukan untuk manusia dan penyusun, mustahil untuk selalu memilih penempatan deallokasi optimal secara sadar, terlepas dari apakah itu proses manual atau otomatis. Dan karena tidak ada yang sempurna, kompiler yang cukup mahir harus mampu mengalahkan manusia dalam memperkirakan penempatan yang optimal. Jadi, ketidakpastian itu bukan alasan mengapa kita membutuhkan pernyataan deallokasi eksplisit .
Ada kasus di mana pengetahuan eksternal menginformasikan penempatan pernyataan deallokasi. Menghapus pernyataan itu sama dengan menghapus bagian dari logika operasional, dan meminta kompiler untuk secara otomatis menghasilkan logika itu sama dengan memintanya untuk menebak apa yang Anda pikirkan.
Misalnya, Anda menulis Read-Evaluate-Print-Loop (REPL) : pengguna mengetikkan perintah, dan program Anda mengeksekusinya. Pengguna dapat mengalokasikan / membatalkan alokasi memori dengan mengetikkan perintah ke dalam REPL Anda. Kode sumber Anda akan menentukan apa yang harus dilakukan REPL untuk setiap perintah pengguna yang mungkin, termasuk deallokasi ketika pengguna mengetikkan perintah untuk itu.
Tetapi jika kode sumber C tidak memberikan perintah eksplisit untuk deallokasi, maka kompiler perlu menyimpulkan bahwa ia harus melakukan dellokasi ketika pengguna memasukkan perintah yang sesuai ke dalam REPL. Apakah perintah itu "membatalkan alokasi", "bebas", atau yang lain? Kompiler tidak memiliki cara untuk mengetahui apa yang Anda inginkan dari perintah. Bahkan jika Anda memprogram dalam logika untuk mencari kata perintah itu dan REPL menemukannya, kompiler tidak memiliki cara untuk mengetahui bahwa itu harus menanggapi dengan deallokasi kecuali Anda secara eksplisit mengatakannya dalam kode sumber.
tl; dr Masalahnya adalah kode sumber C tidak memberikan kompiler pengetahuan eksternal. Ketidakpastian bukan masalah karena itu ada apakah prosesnya manual atau otomatis.
sumber
Saat ini, tidak ada jawaban yang diposting sepenuhnya benar.
Beberapa melakukannya. (Saya akan jelaskan nanti.)
Secara sepele, Anda dapat menelepon
free()
tepat sebelum program keluar. Tetapi ada kebutuhan tersirat dalam pertanyaan Anda untuk meneleponfree()
sesegera mungkin.Masalah kapan harus memanggil
free()
dalam setiap program C segera setelah memori tidak dapat dijangkau tidak dapat ditentukan, yaitu untuk algoritma apa pun yang memberikan jawaban dalam waktu yang terbatas, ada kasus yang tidak tercakup. Ini - dan banyak ketidakpastian lain dari program yang sewenang-wenang - dapat dibuktikan dari Masalah Pemutusan Hubungan .Masalah yang tidak dapat diputuskan tidak selalu dapat diselesaikan dalam waktu yang terbatas oleh algoritma apa pun, apakah itu kompilator atau manusia.
Manusia (mencoba) menulis dalam himpunan bagian dari program C yang dapat diverifikasi untuk kebenaran memori dengan algoritma mereka (sendiri).
Beberapa bahasa mencapai # 1 dengan membangun # 5 ke dalam kompiler. Mereka tidak mengizinkan program dengan penggunaan alokasi memori yang sewenang-wenang, melainkan bagian yang dapat ditentukan dari mereka. Foth dan Rust adalah dua contoh bahasa yang memiliki alokasi memori lebih ketat daripada bahasa C
malloc()
, yang dapat (1) mendeteksi jika suatu program ditulis di luar set yang dapat ditentukan (2) menyisipkan deallokasi secara otomatis.sumber
"Manusia melakukannya, jadi bukan tidak mungkin" adalah kesalahan yang terkenal. Kita tidak perlu memahami (apalagi mengendalikan) hal-hal yang kita buat - uang adalah contoh umum. Kita cenderung melebih-lebihkan (kadang-kadang secara dramatis) peluang keberhasilan kita dalam masalah teknologi, terutama ketika faktor manusia tampaknya tidak ada.
Kinerja manusia dalam pemrograman komputer sangat buruk , dan studi ilmu komputer (kurang dalam banyak program pendidikan profesional) membantu memahami mengapa masalah ini tidak memiliki perbaikan yang sederhana. Kita mungkin suatu hari, mungkin tidak terlalu jauh, digantikan oleh kecerdasan buatan pada pekerjaan. Meski begitu, tidak akan ada algoritma umum yang mendapatkan alokasi yang benar, secara otomatis, setiap saat.
sumber
Kurangnya manajemen memori otomatis adalah fitur bahasa.
C tidak seharusnya menjadi alat untuk menulis perangkat lunak dengan mudah. Ini adalah alat untuk membuat komputer melakukan apa pun yang Anda perintahkan. Itu termasuk mengalokasikan dan membatalkan alokasi memori pada saat yang Anda pilih. C adalah bahasa tingkat rendah yang Anda gunakan saat Anda ingin mengontrol komputer dengan tepat, atau ketika Anda ingin melakukan sesuatu dengan cara yang berbeda dari apa yang diharapkan oleh perancang perpustakaan bahasa / standar.
sumber
Masalahnya sebagian besar adalah artefak bersejarah, bukan ketidakmungkinan implementasi.
Cara kebanyakan kompiler C membuat kode adalah sehingga kompiler hanya melihat setiap file sumber pada satu waktu; tidak pernah melihat keseluruhan program sekaligus. Ketika satu file sumber memanggil fungsi dari file sumber lain atau pustaka, semua kompiler melihat adalah file header dengan jenis fungsi yang dikembalikan, bukan kode sebenarnya dari fungsi tersebut. Ini berarti ketika ada fungsi yang mengembalikan pointer, kompiler tidak memiliki cara untuk mengetahui apakah memori yang menunjuk pointer perlu dibebaskan atau tidak. Informasi untuk memutuskan yang tidak ditampilkan ke kompiler pada saat itu. Seorang programmer manusia, di sisi lain, bebas untuk mencari kode sumber fungsi atau dokumentasi untuk mengetahui apa yang perlu dilakukan dengan pointer.
Jika Anda melihat ke bahasa tingkat rendah yang lebih modern seperti C ++ 11 atau Rust Anda akan menemukan bahwa mereka sebagian besar memecahkan masalah dengan membuat kepemilikan memori eksplisit dalam jenis pointer. Dalam C + + Anda akan menggunakan
unique_ptr<T>
alih - alih dataranT*
untuk menahan memori danunique_ptr<T>
memastikan bahwa memori akan dibebaskan ketika objek mencapai ujung lingkup, tidak seperti dataranT*
. Programmer dapat menyerahkan memori dari satuunique_ptr<T>
ke yang lain, tetapi hanya ada satuunique_ptr<T>
menunjuk pada memori. Jadi, selalu jelas siapa yang memiliki memori dan kapan perlu dibebaskan.C ++, untuk alasan kompatibilitas ke belakang, masih memungkinkan manajemen memori manual gaya lama dan dengan demikian penciptaan bug atau cara untuk menghindari perlindungan a
unique_ptr<T>
. Rust bahkan lebih ketat karena memberlakukan aturan kepemilikan memori melalui kesalahan kompiler.Adapun keraguan, masalah penghentian dan sejenisnya, ya, jika Anda tetap pada semantik C tidak mungkin untuk memutuskan untuk semua program ketika memori harus dibebaskan. Namun untuk sebagian besar program aktual, bukan latihan akademis atau perangkat lunak kereta, sangat mungkin untuk memutuskan kapan harus bebas dan kapan tidak. Bagaimanapun, itulah satu-satunya alasan mengapa manusia dapat mengetahui kapan harus bebas atau tidak.
sumber
Jawaban lain berfokus pada apakah mungkin untuk melakukan pengumpulan sampah, beberapa detail tentang bagaimana hal itu dilakukan, dan beberapa masalah.
Salah satu masalah yang belum dibahas adalah keterlambatan pengumpulan sampah yang tak terhindarkan. Di C, ketika programmer memanggil free (), memori itu segera tersedia untuk digunakan kembali. (Setidaknya dalam teori!) Jadi seorang programmer dapat membebaskan struktur 100MB mereka, mengalokasikan struktur 100MB lain milidetik kemudian, dan mengharapkan penggunaan memori secara keseluruhan tetap sama.
Ini tidak benar dengan pengumpulan sampah. Sistem pengumpulan sampah memiliki beberapa keterlambatan dalam mengembalikan memori yang tidak digunakan ke tumpukan, dan ini bisa menjadi signifikan. Jika struktur 100MB Anda keluar dari ruang lingkup, dan milidetik kemudian, program Anda membuat struktur 100MB lainnya, Anda dapat mengharapkan sistem Anda menggunakan 200MB untuk periode yang singkat. "Periode singkat" itu mungkin milidetik atau detik tergantung pada sistem, tetapi masih ada penundaan.
Jika Anda menjalankan pada PC dengan gigs of RAM dan memori virtual, tentu saja Anda mungkin tidak akan pernah melihat ini. Jika Anda menjalankan sistem dengan sumber daya yang lebih terbatas (katakanlah, sistem yang disematkan atau telepon), ini adalah sesuatu yang perlu Anda perhatikan dengan serius. Ini bukan hanya teoretis - Saya pribadi telah melihat ini membuat masalah (seperti menabrak jenis perangkat masalah) ketika bekerja pada sistem WinCE menggunakan .NET Compact Framework dan berkembang di C #.
sumber
Pertanyaannya menganggap deallokasi adalah sesuatu yang seharusnya disimpulkan oleh programmer dari bagian lain dari kode sumber. Ini bukan. "Pada titik ini dalam program, referensi memori FOO tidak berguna lagi" adalah informasi yang hanya diketahui dalam pikiran programmer sampai itu dikodekan ke dalam (dalam bahasa prosedural) pernyataan deallokasi.
Secara teori tidak berbeda dari baris kode lainnya. Mengapa kompiler tidak secara otomatis memasukkan "Pada titik ini dalam program, periksa register BAR untuk input" atau "jika pemanggilan fungsi mengembalikan nol, keluar dari subrutin saat ini" ? Dari sudut pandang kompiler alasannya adalah "ketidaklengkapan", seperti yang ditunjukkan dalam jawaban ini . Tetapi setiap program mengalami ketidaklengkapan ketika programmer belum menceritakan semua yang dia tahu.
Dalam kehidupan nyata, deallocation adalah pekerjaan kasar atau boilerplate; otak kita mengisinya secara otomatis dan menggerutu tentang hal itu, dan sentimen "kompiler dapat melakukannya dengan baik atau lebih baik" adalah benar. Namun secara teori, bukan itu masalahnya, walaupun untungnya bahasa lain memberi kita lebih banyak pilihan teori.
sumber
Apa yang dilakukan: Ada pengumpulan sampah, dan ada kompiler yang menggunakan penghitungan referensi (Objective-C, Swift). Mereka yang melakukan penghitungan referensi membutuhkan bantuan dari programmer dengan menghindari siklus referensi yang kuat.
The nyata jawaban "mengapa" adalah bahwa penulis compiler belum tahu cara yang cukup baik dan cukup cepat untuk membuatnya dapat digunakan dalam kompilator. Karena penulis kompiler biasanya cukup pintar, Anda dapat menyimpulkan bahwa sangat, sangat sulit untuk menemukan cara yang cukup baik dan cukup cepat.
Salah satu alasan mengapa hal itu sangat, sangat sulit tentu saja tidak dapat diputuskan. Dalam ilmu komputer, ketika kita berbicara tentang "decidability" yang kita maksudkan adalah "membuat keputusan yang tepat". Pemrogram manusia tentu saja dapat dengan mudah memutuskan di mana harus mengalokasikan memori, karena mereka tidak terbatas pada keputusan yang benar . Dan mereka sering membuat keputusan yang salah.
sumber
Karena masa pakai memory block adalah keputusan programmer, bukan compiler.
Itu dia. Ini adalah desain C. Compiler tidak dapat mengetahui apa tujuan mengalokasikan blok memori. Manusia dapat melakukannya, karena mereka tahu tujuan setiap blok memori dan kapan tujuan ini dilayani sehingga dapat dibebaskan. Itu bagian dari desain program yang sedang ditulis.
C adalah bahasa tingkat rendah, jadi contoh untuk melewatkan blok memori Anda ke proses lain atau bahkan ke prosesor lain cukup sering. Dalam kasus ekstrim, seorang programmer dapat dengan sengaja mengalokasikan sejumlah memori dan tidak pernah menggunakannya lagi hanya untuk memberikan tekanan memori pada bagian lain dari sistem. Compiler tidak memiliki cara untuk mengetahui apakah blok tersebut masih diperlukan.
sumber
Dalam C dan banyak bahasa lain, memang ada fasilitas untuk membuat kompiler melakukan hal yang sama dalam hal ini di mana hal itu jelas pada waktu kompilasi ketika harus dilakukan: penggunaan variabel durasi otomatis (yaitu variabel lokal biasa) . Compiler bertanggung jawab untuk mengatur ruang yang cukup untuk variabel-variabel tersebut, dan untuk melepaskan ruang itu ketika masa hidup mereka (yang didefinisikan dengan baik) berakhir.
Dengan array panjang variabel menjadi fitur C sejak C99, objek durasi otomatis melayani, pada prinsipnya, secara substansial semua fungsi di C yang dilakukan objek yang secara dinamis dialokasikan untuk durasi yang dikomputasi. Dalam praktiknya, tentu saja, implementasi C dapat menempatkan batasan praktis yang signifikan dalam penggunaan VLA - yaitu ukurannya mungkin terbatas sebagai akibat dialokasikan pada tumpukan - tetapi ini adalah pertimbangan implementasi, bukan pertimbangan desain bahasa.
Objek-objek yang tujuan penggunaannya tidak memberi mereka durasi otomatis adalah mereka yang seumur hidupnya tidak dapat ditentukan pada waktu kompilasi.
sumber