Saya akan memulai dengan mengatakan, gunakan pointer pintar dan Anda tidak perlu khawatir tentang ini.
Apa masalah dengan kode berikut?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
Ini dipicu oleh jawaban dan komentar untuk pertanyaan lain. Satu komentar dari Neil Butterworth menghasilkan beberapa upvotes:
Menetapkan pointer ke NULL setelah penghapusan bukanlah praktik universal yang baik di C ++. Ada saat-saat itu adalah hal yang baik untuk dilakukan, dan saat-saat itu tidak ada gunanya dan dapat menyembunyikan kesalahan.
Ada banyak keadaan di mana itu tidak akan membantu. Tapi menurut pengalaman saya, tidak ada salahnya. Seseorang mencerahkan saya.
c++
pointers
null
dynamic-allocation
Mark tebusan
sumber
sumber
delete
Meskipun demikian, aman untuk pointer nol, yang merupakan salah satu alasan untuk mengarahkan pointer bisa baik.Jawaban:
Mengatur pointer ke 0 (yang merupakan "null" dalam standar C ++, NULL define dari C agak berbeda) menghindari crash pada double delete.
Pertimbangkan yang berikut ini:
Sedangkan:
Dengan kata lain, jika Anda tidak menetapkan pointer yang dihapus ke 0, Anda akan mendapat masalah jika Anda melakukan penghapusan ganda. Argumen yang menentang pengaturan pointer ke 0 setelah delete adalah bahwa hal itu hanya menyembunyikan bug hapus ganda dan membiarkannya tidak tertangani.
Yang terbaik adalah tidak memiliki bug hapus ganda, tetapi tergantung pada kepemilikan semantik dan daur hidup objek, ini bisa sulit dicapai dalam praktiknya. Saya lebih suka bug hapus ganda bertopeng dari pada UB.
Akhirnya, sidenote tentang mengelola alokasi objek, saya sarankan Anda melihat
std::unique_ptr
untuk kepemilikan yang ketat / tunggal,std::shared_ptr
untuk kepemilikan bersama, atau implementasi smart pointer lain, tergantung pada kebutuhan Anda.sumber
NULL
bisa menutupi bug hapus ganda. (Beberapa orang mungkin menganggap topeng ini benar-benar solusi - itu, tapi bukan yang sangat bagus karena tidak sampai ke akar masalahnya.) Tetapi tidak mengaturnya ke topeng NULL jauh (JAUH!) masalah umum dalam mengakses data setelah data dihapus.unique_ptr
, yang melakukan apa yangauto_ptr
coba dilakukan, dengan semantik bergerak.Menetapkan pointer ke NULL setelah Anda menghapus apa yang ditunjuknya tentu tidak ada salahnya, tetapi sering kali sedikit bantuan band atas masalah yang lebih mendasar: Mengapa Anda menggunakan pointer di tempat pertama? Saya dapat melihat dua alasan umum:
std::vector
kerjanya, dan itu memecahkan masalah secara tidak sengaja meninggalkan pointer ke memori di sekitar. Tidak ada petunjuk.new
mungkin tidak sama dengan yangdelete
dipanggil. Beberapa objek mungkin telah menggunakan objek secara bersamaan. Dalam hal ini, pointer bersama atau sesuatu yang serupa lebih disukai.Aturan praktis saya adalah bahwa jika Anda meninggalkan pointer di dalam kode pengguna, Anda Melakukannya Salah. Pointer seharusnya tidak ada di sana untuk menunjuk ke sampah di tempat pertama. Mengapa tidak ada objek yang bertanggung jawab untuk memastikan validitasnya? Mengapa cakupannya tidak berakhir ketika objek menunjuk-ke tidak?
sumber
new
.Saya punya praktik terbaik yang lebih baik: Jika memungkinkan, akhiri ruang lingkup variabel!
sumber
Saya selalu mengatur pointer ke
NULL
(sekarangnullptr
) setelah menghapus objek yang ditunjuknya.Ini dapat membantu menangkap banyak referensi untuk membebaskan memori (dengan asumsi kesalahan platform Anda pada deref dari pointer nol).
Ini tidak akan menangkap semua referensi ke memori bebas jika, misalnya, Anda memiliki salinan pointer yang tergeletak di sekitar. Tetapi beberapa lebih baik daripada tidak sama sekali.
Ini akan menutupi double-delete, tapi saya menemukan itu jauh lebih jarang daripada mengakses memori yang sudah dibebaskan.
Dalam banyak kasus, kompiler akan mengoptimalkannya. Jadi argumen bahwa itu tidak perlu tidak meyakinkan saya.
Jika Anda sudah menggunakan RAII, maka tidak ada banyak
delete
kode di awal Anda, jadi argumen bahwa tugas tambahan menyebabkan kekacauan tidak meyakinkan saya.Sering kali nyaman, ketika debugging, untuk melihat nilai nol daripada pointer basi.
Jika ini masih mengganggu Anda, gunakan pointer cerdas atau referensi saja.
Saya juga mengatur jenis pegangan sumber daya lainnya ke nilai tanpa sumber daya saat sumber daya dibebaskan (yang biasanya hanya dalam destruktor pembungkus RAII yang ditulis untuk merangkum sumber daya).
Saya bekerja pada produk komersial besar (9 juta pernyataan) (terutama di C). Pada satu titik, kami menggunakan sihir makro untuk membatalkan kursor ketika memori dibebaskan. Ini segera memaparkan banyak bug yang mengintai yang segera diperbaiki. Sejauh yang saya ingat, kami tidak pernah memiliki bug bebas ganda.
Pembaruan: Microsoft percaya bahwa ini adalah praktik yang baik untuk keamanan dan merekomendasikan praktik ini dalam kebijakan SDL mereka. Rupanya MSVC ++ 11 akan menginjak pointer yang dihapus secara otomatis (dalam banyak keadaan) jika Anda mengkompilasi dengan opsi / SDL.
sumber
Pertama, ada banyak pertanyaan yang ada tentang hal ini dan topik yang terkait erat, misalnya Mengapa tidak menghapus setel pointer ke NULL? .
Dalam kode Anda, masalah apa yang terjadi di (gunakan p). Misalnya, jika di suatu tempat Anda memiliki kode seperti ini:
kemudian mengatur p ke NULL hanya menghasilkan sedikit, karena Anda masih memiliki pointer p2 yang perlu dikhawatirkan.
Ini bukan untuk mengatakan bahwa menetapkan pointer ke NULL selalu sia-sia. Misalnya, jika p adalah variabel anggota yang menunjuk ke sumber daya yang masa pakainya tidak persis sama dengan kelas yang berisi p, maka pengaturan p ke NULL bisa menjadi cara yang berguna untuk menunjukkan ada atau tidaknya sumber daya.
sumber
Jika ada lebih banyak kode setelah
delete
, Ya. Ketika pointer dihapus di konstruktor atau di akhir metode atau fungsi, No.Maksud dari perumpamaan ini adalah untuk mengingatkan programmer, saat run-time, bahwa objek telah dihapus.
Praktek yang lebih baik lagi adalah dengan menggunakan Smart Pointer (dibagikan atau dicakup) yang secara otomatis menghapus objek target mereka.
sumber
Seperti yang orang lain katakan,
delete ptr; ptr = 0;
tidak akan menyebabkan iblis terbang keluar dari hidung Anda. Namun, hal itu mendorong penggunaanptr
sebagai semacam bendera. Kode menjadi berserakandelete
dan mengatur pointer keNULL
. Langkah selanjutnya adalah menyebarkanif (arg == NULL) return;
kode Anda untuk melindungi dari penggunaanNULL
pointer secara tidak sengaja . Masalah terjadi setelah pemeriksaan terhadapNULL
menjadi sarana utama Anda untuk memeriksa keadaan suatu objek atau program.Saya yakin ada bau kode tentang menggunakan pointer sebagai bendera di suatu tempat tapi saya belum menemukannya.
sumber
NULL
bukan nilai yang valid, maka Anda mungkin harus menggunakan referensi.Saya akan sedikit mengubah pertanyaan Anda:
Ada dua skenario di mana pengaturan pointer ke NULL dapat dilewati:
Sementara itu, berargumen bahwa pengaturan pointer ke NULL mungkin menyembunyikan kesalahan kepada saya terdengar seperti berargumen bahwa Anda tidak boleh memperbaiki bug karena perbaikan mungkin menyembunyikan bug lain. Satu-satunya bug yang mungkin ditampilkan jika pointer tidak disetel ke NULL adalah bug yang mencoba menggunakan pointer. Tetapi mengaturnya ke NULL sebenarnya akan menyebabkan bug yang sama persis seperti yang akan ditampilkan jika Anda menggunakannya dengan memori yang dibebaskan, bukan?
sumber
Jika Anda tidak memiliki kendala lain yang memaksa Anda untuk mengatur atau tidak mengatur pointer ke NULL setelah Anda menghapusnya (salah satu kendala tersebut disebutkan oleh Neil Butterworth ), maka preferensi pribadi saya adalah membiarkannya.
Bagi saya, pertanyaannya bukan "apakah ini ide yang bagus?" tetapi "perilaku apa yang akan saya cegah atau biarkan berhasil dengan melakukan ini?" Sebagai contoh, jika ini memungkinkan kode lain untuk melihat bahwa pointer tidak lagi tersedia, mengapa kode lain bahkan berusaha untuk melihat pointer yang dibebaskan setelah mereka dibebaskan? Biasanya, ini adalah bug.
Ini juga bekerja lebih dari yang diperlukan serta menghambat debugging post-mortem. Semakin sedikit Anda menyentuh memori setelah Anda tidak membutuhkannya, semakin mudah untuk mencari tahu mengapa sesuatu jatuh. Banyak kali saya mengandalkan fakta bahwa memori berada dalam kondisi yang mirip dengan ketika bug tertentu terjadi untuk mendiagnosis dan memperbaiki bug tersebut.
sumber
Secara eksplisit membatalkan setelah hapus sangat menyarankan kepada pembaca bahwa pointer mewakili sesuatu yang secara konseptual opsional . Jika saya melihat itu dilakukan, saya akan mulai khawatir bahwa di mana-mana di sumber pointer akan digunakan yang harus diuji terlebih dahulu terhadap NULL.
Jika itu yang Anda maksud, lebih baik buat yang eksplisit di sumber menggunakan sesuatu seperti boost :: opsional
Tetapi jika Anda benar-benar ingin orang tahu bahwa pointernya telah "rusak", saya akan menyatakan 100% setuju dengan mereka yang mengatakan hal terbaik untuk dilakukan adalah membuatnya keluar dari ruang lingkup. Kemudian Anda menggunakan kompiler untuk mencegah kemungkinan dereferensi buruk saat runtime.
Itu bayi di semua air mandi C ++, tidak harus dibuang. :)
sumber
Dalam program yang terstruktur dengan baik dengan pemeriksaan kesalahan yang sesuai, tidak ada alasan untuk tidak menetapkannya nol.
0
berdiri sendiri sebagai nilai tidak valid yang diakui secara universal dalam konteks ini. Gagal dan gagal segera.Banyak argumen yang menentang penetapan
0
menunjukkan bahwa itu bisa menyembunyikan bug atau mempersulit aliran kontrol. Pada dasarnya, itu adalah kesalahan hulu (bukan kesalahan Anda (maaf untuk permainan buruk)) atau kesalahan lain atas nama programmer - bahkan mungkin indikasi bahwa aliran program telah tumbuh terlalu kompleks.Jika programmer ingin memperkenalkan penggunaan pointer yang mungkin nol sebagai nilai khusus dan menulis semua yang perlu dihindari, itu adalah komplikasi yang sengaja mereka perkenalkan. Semakin baik karantina, semakin cepat Anda menemukan kasus penyalahgunaan, dan semakin sedikit mereka dapat menyebar ke program lain.
Program yang terstruktur dengan baik dapat dirancang menggunakan fitur C ++ untuk menghindari kasus ini. Anda dapat menggunakan referensi, atau Anda bisa mengatakan "meneruskan / menggunakan argumen nol atau tidak valid adalah kesalahan" - sebuah pendekatan yang sama-sama berlaku untuk wadah, seperti smart pointer. Meningkatkan perilaku yang konsisten dan benar mencegah bug ini dari jauh.
Dari sana, Anda hanya memiliki ruang lingkup dan konteks yang sangat terbatas di mana penunjuk nol mungkin ada (atau diizinkan).
Hal yang sama dapat diterapkan pada pointer yang tidak
const
. Mengikuti nilai dari sebuah pointer adalah sepele karena cakupannya sangat kecil, dan penggunaan yang tidak benar diperiksa dan didefinisikan dengan baik. Jika perangkat dan insinyur Anda tidak dapat mengikuti program setelah membaca cepat atau ada pemeriksaan kesalahan yang tidak tepat atau aliran program tidak konsisten / lunak, Anda memiliki masalah lain yang lebih besar.Akhirnya, kompiler dan lingkungan Anda mungkin memiliki beberapa penjaga saat Anda ingin memperkenalkan kesalahan (coretan), mendeteksi akses ke memori yang dibebaskan, dan menangkap UB terkait lainnya. Anda juga dapat memperkenalkan diagnostik serupa ke dalam program Anda, seringkali tanpa mempengaruhi program yang ada.
sumber
Biarkan saya memperluas apa yang sudah Anda masukkan ke pertanyaan Anda.
Inilah yang Anda masukkan ke dalam pertanyaan Anda, dalam bentuk bullet-point:
Menetapkan pointer ke NULL setelah penghapusan bukanlah praktik universal yang baik di C ++. Ada kalanya:
Namun, tidak ada saat ketika ini buruk ! Anda tidak akan memperkenalkan lebih banyak bug dengan membatalkannya secara eksplisit, Anda tidak akan membocorkan memori, Anda tidak akan menyebabkan perilaku tidak terdefinisi terjadi.
Jadi, jika ragu, batalkan saja.
Karena itu, jika Anda merasa bahwa Anda harus secara eksplisit membatalkan beberapa penunjuk, maka bagi saya ini terdengar seperti Anda belum membagi metode, dan harus melihat pendekatan refactoring yang disebut "Ekstrak metode" untuk membagi metode menjadi bagian yang terpisah.
sumber
Iya.
Satu-satunya "kerugian" yang dapat dilakukannya adalah memasukkan inefisiensi (operasi toko yang tidak perlu) ke dalam program Anda - tetapi overhead ini tidak signifikan dalam kaitannya dengan biaya pengalokasian dan pembebasan blok memori dalam banyak kasus.
Jika Anda tidak melakukannya, suatu hari Anda akan memiliki bug derefernce pointer jahat.
Saya selalu menggunakan makro untuk menghapus:
(dan serupa untuk array, gratis (), melepaskan pegangan)
Anda juga dapat menulis metode "hapus sendiri" yang mengambil referensi ke penunjuk kode panggilan, sehingga mereka memaksa penunjuk kode panggilan ke NULL. Misalnya, untuk menghapus subtree dari banyak objek:
sunting
Ya, teknik-teknik ini memang melanggar beberapa aturan tentang penggunaan makro (dan ya, hari ini Anda mungkin bisa mencapai hasil yang sama dengan templat) - tetapi dengan menggunakan bertahun-tahun saya tidak pernah mengakses memori mati - salah satu yang paling jahat dan paling sulit dan kebanyakan memakan waktu untuk debug masalah yang bisa Anda hadapi. Dalam praktik selama bertahun-tahun mereka telah secara efektif menghilangkan kelas bug bug dari setiap tim yang saya kenalkan.
Ada juga banyak cara Anda dapat menerapkan hal di atas - Saya hanya mencoba untuk menggambarkan ide memaksa orang untuk NULL pointer jika mereka menghapus objek, daripada memberikan cara bagi mereka untuk melepaskan memori yang tidak NULL pointer pemanggil .
Tentu saja, contoh di atas hanyalah langkah menuju penunjuk otomatis. Yang saya tidak menyarankan karena OP secara khusus bertanya tentang kasus tidak menggunakan pointer otomatis.
sumber
anObject->Delete(anObject)
invalidate theanObject
pointer. Itu hanya menakutkan. Anda harus membuat metode statis untuk ini sehingga Anda paling tidak harus melakukannyaTreeItem::Delete(anObject)
."Ada saat-saat itu adalah hal yang baik untuk dilakukan, dan saat-saat itu tidak ada gunanya dan dapat menyembunyikan kesalahan"
Saya dapat melihat dua masalah: Kode sederhana itu:
menjadi for-liner di lingkungan multithreaded:
"Praktik terbaik" dari Don Neufeld tidak berlaku selalu. Misalnya dalam satu proyek otomotif kita harus menetapkan pointer ke 0 bahkan pada destruktor. Saya bisa membayangkan dalam perangkat lunak keamanan-kritis aturan seperti itu tidak jarang. Lebih mudah (dan bijak) untuk mengikuti mereka daripada mencoba membujuk tim / pemeriksa kode untuk setiap penggunaan pointer dalam kode, bahwa garis yang membatalkan pointer ini berlebihan.
Bahaya lain adalah mengandalkan teknik ini dalam kode yang menggunakan pengecualian:
Dalam kode seperti itu Anda menghasilkan kebocoran sumber daya dan menunda masalah atau proses macet.
Jadi, dua masalah ini terjadi secara spontan di kepala saya (Herb Sutter pasti akan mengatakan lebih banyak) membuat bagi saya semua pertanyaan dari jenis "Bagaimana menghindari menggunakan smart-pointer dan melakukan pekerjaan dengan aman dengan pointer normal" sebagai usang.
sumber
Selalu ada Pointer Menggantung yang perlu dikhawatirkan.
sumber
Jika Anda akan merealokasi pointer sebelum menggunakannya lagi (mendereferensinya, meneruskannya ke fungsi, dll.), Membuat pointer NULL hanyalah operasi tambahan. Namun, jika Anda tidak yakin apakah itu akan dialokasikan kembali atau tidak sebelum digunakan lagi, mengaturnya ke NULL adalah ide yang bagus.
Seperti yang telah banyak dikatakan, tentu saja jauh lebih mudah untuk hanya menggunakan pointer pintar.
Sunting: Seperti yang dikatakan Thomas Matthews dalam jawaban sebelumnya , jika sebuah pointer dihapus dalam sebuah destruktor, tidak perlu untuk menetapkan NULL padanya karena itu tidak akan digunakan lagi karena objek tersebut sudah dihancurkan.
sumber
Saya bisa membayangkan mengatur pointer ke NULL setelah menghapusnya berguna dalam kasus yang jarang terjadi di mana ada skenario yang sah untuk menggunakannya kembali dalam satu fungsi (atau objek). Kalau tidak masuk akal - pointer perlu menunjuk ke sesuatu yang bermakna selama itu ada - titik.
sumber
Jika kode itu bukan bagian paling kritis kinerja aplikasi Anda, buat tetap sederhana dan gunakan shared_ptr:
Ia melakukan penghitungan referensi dan aman digunakan. Anda dapat menemukannya di tr1 (std :: tr1 namespace, #include <memory>) atau jika kompiler Anda tidak menyediakannya, dapatkan dari boost.
sumber