Apakah praktik yang baik untuk NULL pointer setelah menghapusnya?


150

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.


6
@Andre: Secara teknis, tidak terdefinisi. Apa yang mungkin terjadi adalah Anda mengakses memori yang sama seperti sebelumnya, tetapi sekarang dapat digunakan oleh hal lain. Jika Anda menghapus memori dua kali, kemungkinan untuk mengacaukan eksekusi program Anda dengan cara yang sulit ditemukan. deleteMeskipun demikian, aman untuk pointer nol, yang merupakan salah satu alasan untuk mengarahkan pointer bisa baik.
David Thornley

5
@ André Pena, tidak ditentukan. Seringkali bahkan tidak dapat diulang. Anda mengatur pointer ke NULL untuk membuat kesalahan lebih terlihat saat debugging, dan mungkin membuatnya lebih berulang.
Mark Ransom

3
@ André: Tidak ada yang tahu. Itu adalah Perilaku Tidak Terdefinisi. Mungkin macet dengan pelanggaran akses, atau mungkin menimpa memori yang digunakan oleh sisa aplikasi. Standar bahasa tidak menjamin apa yang terjadi, dan karenanya Anda tidak dapat memercayai aplikasi Anda begitu aplikasi itu terjadi. Itu bisa menembakkan rudal nuklir atau memformat harddisk Anda. itu dapat merusak memori aplikasi Anda, atau mungkin membuat setan terbang keluar dari hidung Anda. Semua taruhan dibatalkan.
jalf

16
Setan terbang adalah fitur, bukan bug.
jball

3
Pertanyaan ini bukan duplikat karena pertanyaan lain adalah tentang C dan yang ini adalah tentang C ++. Banyak jawaban bergantung pada hal-hal seperti pointer pintar, yang tidak tersedia di C ++.
Adrian McCarthy

Jawaban:


86

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:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

Sedangkan:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

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_ptruntuk kepemilikan yang ketat / tunggal, std::shared_ptruntuk kepemilikan bersama, atau implementasi smart pointer lain, tergantung pada kebutuhan Anda.


13
Aplikasi Anda tidak akan selalu macet pada penghapusan ganda. Bergantung pada apa yang terjadi di antara kedua penghapusan, apa pun bisa terjadi. Kemungkinan besar, Anda akan merusak tumpukan Anda, dan Anda akan crash pada suatu saat nanti dalam sepotong kode yang sama sekali tidak terkait. Sementara segfault biasanya lebih baik daripada diam-diam mengabaikan kesalahan, segfault tidak dijamin dalam kasus ini, dan itu utilitas yang dipertanyakan.
Adam Rosenfield

28
Masalahnya di sini adalah fakta bahwa Anda memiliki penghapusan ganda. Membuat pointer NULL menyembunyikan fakta itu tidak memperbaikinya atau membuatnya lebih aman. Bayangkan seorang pemimpin datang kembali setahun kemudian dan melihat foo dihapus. Dia sekarang percaya bahwa dia dapat menggunakan kembali pointer, sayangnya dia mungkin melewatkan penghapusan kedua (bahkan mungkin tidak dalam fungsi yang sama) dan sekarang penggunaan kembali pointer sekarang menjadi sampah oleh penghapusan kedua. Akses apa pun setelah penghapusan kedua sekarang menjadi masalah besar.
Martin York

11
Memang benar pengaturan pointer untuk NULLbisa 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.
Adrian McCarthy

AFAIK, std :: auto_ptr telah ditinggalkan dalam standar c ++ yang akan datang
rafak

Saya tidak akan mengatakan usang, itu membuatnya terdengar seperti ide itu hilang begitu saja. Sebaliknya, itu diganti dengan unique_ptr, yang melakukan apa yang auto_ptrcoba dilakukan, dengan semantik bergerak.
GManNickG

55

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:

  • Anda hanya ingin sesuatu dialokasikan pada heap. Dalam hal membungkusnya dalam objek RAII akan jauh lebih aman dan bersih. Akhiri ruang lingkup objek RAII saat Anda tidak lagi membutuhkan objek. Begitulah cara std::vectorkerjanya, dan itu memecahkan masalah secara tidak sengaja meninggalkan pointer ke memori di sekitar. Tidak ada petunjuk.
  • Atau mungkin Anda menginginkan semantik kepemilikan bersama yang rumit. Pointer yang dikembalikan dari newmungkin tidak sama dengan yang deletedipanggil. 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?


15
Jadi Anda membuat argumen bahwa seharusnya tidak ada pointer mentah di tempat pertama, dan apa pun yang melibatkan pointer mengatakan tidak boleh diberkati dengan istilah "praktik yang baik"? Cukup adil.
Mark Ransom

7
Ya kurang lebih. Saya tidak akan mengatakan bahwa tidak ada yang melibatkan pointer mentah dapat disebut praktik yang baik. Hanya saja itu pengecualian dan bukan aturan. Biasanya, keberadaan pointer adalah indikator bahwa ada sesuatu yang salah pada level yang lebih dalam.
jalf

3
tetapi untuk menjawab pertanyaan langsung, tidak, saya tidak melihat bagaimana pengaturan pointer ke null dapat menyebabkan kesalahan.
jalf

7
Saya tidak setuju - ada kasus di mana pointer baik untuk digunakan. Misalnya, ada 2 variabel di tumpukan dan Anda ingin memilih salah satunya. Atau Anda ingin meneruskan variabel opsional ke suatu fungsi. Saya akan mengatakan, Anda tidak boleh menggunakan pointer mentah dalam hubungannya dengan new.
rlbond

4
ketika pointer telah keluar dari ruang lingkup, saya tidak melihat bagaimana sesuatu atau siapa pun mungkin perlu menghadapinya.
Jalf

43

Saya punya praktik terbaik yang lebih baik: Jika memungkinkan, akhiri ruang lingkup variabel!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}

17
Ya, RAII adalah temanmu. Bungkus dalam kelas dan itu menjadi lebih sederhana. Atau jangan atasi memori Anda sendiri dengan menggunakan STL!
Brian

24
Ya memang, itu pilihan terbaik. Namun tidak menjawab pertanyaan.
Mark Ransom

3
Ini tampaknya hanya menjadi produk sampingan dari menggunakan periode fungsi lingkup, dan tidak benar-benar mengatasi masalah. Ketika Anda menggunakan pointer, Anda biasanya mengirimkan salinan dari mereka beberapa lapisan dalam dan kemudian metode Anda benar-benar tidak berarti dalam upaya untuk mengatasi masalah tersebut. Meskipun saya setuju bahwa desain yang baik akan membantu Anda mengisolasi kesalahan, saya tidak berpikir bahwa metode Anda adalah sarana utama untuk tujuan itu.
San Jacinto

2
Kalau dipikir-pikir, jika Anda bisa melakukan ini, mengapa Anda tidak akan melupakan tumpukan dan menarik semua memori Anda dari tumpukan?
San Jacinto

4
Contoh saya sengaja minimal. Misalnya, alih-alih yang baru, mungkin objek tersebut dibuat oleh pabrik, dalam hal ini tidak dapat masuk ke tumpukan. Atau mungkin itu tidak dibuat di awal ruang lingkup, tetapi terletak di beberapa struktur. Apa yang saya ilustrasikan adalah bahwa pendekatan ini akan menemukan penyalahgunaan pointer pada waktu kompilasi , sedangkan NULLing akan menemukan penyalahgunaan pada waktu berjalan .
Don Neufeld

31

Saya selalu mengatur pointer ke NULL(sekarang nullptr) setelah menghapus objek yang ditunjuknya.

  1. Ini dapat membantu menangkap banyak referensi untuk membebaskan memori (dengan asumsi kesalahan platform Anda pada deref dari pointer nol).

  2. 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.

  3. Ini akan menutupi double-delete, tapi saya menemukan itu jauh lebih jarang daripada mengakses memori yang sudah dibebaskan.

  4. Dalam banyak kasus, kompiler akan mengoptimalkannya. Jadi argumen bahwa itu tidak perlu tidak meyakinkan saya.

  5. Jika Anda sudah menggunakan RAII, maka tidak ada banyak deletekode di awal Anda, jadi argumen bahwa tugas tambahan menyebabkan kekacauan tidak meyakinkan saya.

  6. Sering kali nyaman, ketika debugging, untuk melihat nilai nol daripada pointer basi.

  7. 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.


12

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:

Foo * p2 = p;

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.


1
Saya setuju bahwa ada saat-saat itu tidak akan membantu, tetapi Anda tampaknya menyiratkan bahwa itu bisa berbahaya secara aktif. Apakah itu maksud Anda, atau apakah saya salah membacanya?
Mark Ransom

1
Apakah ada salinan pointer tidak relevan dengan pertanyaan apakah variabel pointer harus disetel ke NULL. Menetapkannya ke NULL adalah praktik yang baik dengan alasan yang sama membersihkan piring setelah Anda selesai makan malam adalah praktik yang baik - sementara itu bukan perlindungan terhadap semua bug yang bisa dimiliki kode, itu mempromosikan kesehatan kode yang baik.
Franci Penov

2
@ Franci Banyak orang tampaknya tidak setuju dengan Anda. Dan apakah ada salinan tentu relevan jika Anda mencoba menggunakan salinan setelah Anda menghapus aslinya.

3
Franci, ada perbedaan. Anda membersihkan piring karena Anda menggunakannya lagi. Anda tidak perlu pointer setelah Anda menghapusnya. Itu harus menjadi hal terakhir yang Anda lakukan. Praktik yang lebih baik adalah menghindari situasi sama sekali.
GManNickG

1
Anda bisa menggunakan kembali variabel, tetapi itu bukan lagi kasus pemrograman defensif; itu bagaimana Anda merancang solusi untuk masalah yang dihadapi. OP sedang mendiskusikan apakah gaya defensif ini adalah sesuatu yang harus kita perjuangkan, bukan dari kita akan pernah menetapkan pointer ke nol. Dan idealnya, untuk pertanyaan Anda, ya! Jangan gunakan pointer setelah Anda menghapusnya!
GManNickG

7

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.


Semua orang (termasuk si penanya asli) setuju bahwa petunjuk pintar adalah jalan yang harus ditempuh. Kode berkembang. Mungkin tidak ada lagi kode setelah penghapusan saat pertama kali Anda memperbaikinya, tetapi kemungkinan akan berubah seiring waktu. Menempatkan dalam tugas membantu ketika itu terjadi (dan hampir tidak ada biaya dalam waktu yang berarti).
Adrian McCarthy

3

Seperti yang orang lain katakan, delete ptr; ptr = 0;tidak akan menyebabkan iblis terbang keluar dari hidung Anda. Namun, hal itu mendorong penggunaan ptrsebagai semacam bendera. Kode menjadi berserakan deletedan mengatur pointer ke NULL. Langkah selanjutnya adalah menyebarkan if (arg == NULL) return;kode Anda untuk melindungi dari penggunaan NULLpointer secara tidak sengaja . Masalah terjadi setelah pemeriksaan terhadap NULLmenjadi 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.


9
Tidak ada yang salah dengan menggunakan pointer sebagai bendera. Jika Anda menggunakan pointer, dan NULLbukan nilai yang valid, maka Anda mungkin harus menggunakan referensi.
Adrian McCarthy

2

Saya akan sedikit mengubah pertanyaan Anda:

Apakah Anda menggunakan pointer yang tidak diinisialisasi? Anda tahu, yang tidak Anda atur ke NULL atau alokasikan memori yang ditunjuknya?

Ada dua skenario di mana pengaturan pointer ke NULL dapat dilewati:

  • variabel pointer segera keluar dari ruang lingkup
  • Anda telah membebani semantik pointer dan menggunakan nilainya tidak hanya sebagai penunjuk memori, tetapi juga sebagai nilai kunci atau mentah. Namun pendekatan ini menderita masalah lain.

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?


(A) "terdengar seperti berargumen bahwa Anda tidak boleh memperbaiki bug" Tidak mengatur pointer ke NULL bukan bug. (B) "Tetapi mengaturnya ke NULL akan benar-benar menyebabkan bug yang sama persis" Tidak. Pengaturan ke NULL menyembunyikan penghapusan ganda . (C) Ringkasan: Mengatur ke NULL menyembunyikan penghapusan ganda, tetapi memperlihatkan referensi basi. Tidak menetapkan ke NULL dapat menyembunyikan referensi basi, tetapi memperlihatkan penghapusan ganda. Kedua belah pihak sepakat bahwa masalah sebenarnya adalah untuk memperbaiki referensi basi dan menghapus ganda.
Mooing Duck

2

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.


2

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

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

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. :)


2

Dalam program yang terstruktur dengan baik dengan pemeriksaan kesalahan yang sesuai, tidak ada alasan untuk tidak menetapkannya nol. 0berdiri sendiri sebagai nilai tidak valid yang diakui secara universal dalam konteks ini. Gagal dan gagal segera.

Banyak argumen yang menentang penetapan 0menunjukkan 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.


1

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:

  • itu hal yang baik untuk dilakukan
  • dan saat-saat ketika itu tidak ada gunanya dan bisa menyembunyikan kesalahan.

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.


Saya tidak setuju dengan "tidak ada saat ketika ini buruk." Pertimbangkan jumlah cutter yang diperkenalkan idiom ini. Anda memiliki tajuk yang disertakan dalam setiap unit yang menghapus sesuatu, dan semua lokasi yang dihapus menjadi sedikit lebih mudah.
GManNickG

Ada kalanya itu buruk. Jika seseorang mencoba mengubah referensi pointer yang dihapus-sekarang-nol Anda ketika tidak seharusnya, itu mungkin tidak akan crash dan bug itu 'disembunyikan.' Jika mereka mengubah pointer yang dihapus Anda yang masih memiliki beberapa nilai acak di dalamnya, Anda mungkin akan melihat dan bug akan lebih mudah dilihat.
Carson Myers

@Carson: Pengalaman saya justru sebaliknya: Dereferencing nullptr akan hampir semua cara crash Aplikasi dan dapat ditangkap oleh debugger. Menereferensi pointer yang menggantung biasanya tidak langsung menghasilkan masalah, tetapi sering kali hanya akan menyebabkan hasil yang salah atau kesalahan lain di telepon.
MikeMB

@MikeMB Saya sepenuhnya setuju, pandangan saya tentang ini telah berubah secara substansial di masa lalu ~ 6,5 tahun
Carson Myers

Dalam hal menjadi programmer, kami semua adalah orang lain 6-7 tahun yang lalu :) Saya bahkan tidak yakin saya berani menjawab pertanyaan C / C ++ hari ini :)
Lasse V. Karlsen

1

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:

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(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:

static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
    if (rootObject == NULL)
        return;

    rootObject->UnlinkFromParent();

    for (int i = 0; i < numChildren)
       DeleteSubtree(rootObject->child[i]);

    delete rootObject;
    rootObject = NULL;
}

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.


2
Macro adalah ide yang buruk, terutama ketika mereka terlihat seperti fungsi normal. Jika Anda ingin melakukan ini, gunakan fungsi templated.

2
Wow ... Saya belum pernah melihat yang seperti anObject->Delete(anObject)invalidate the anObjectpointer. Itu hanya menakutkan. Anda harus membuat metode statis untuk ini sehingga Anda paling tidak harus melakukannya TreeItem::Delete(anObject).
D.Shawley

Maaf, diketikkan sebagai fungsi alih-alih menggunakan huruf besar yang tepat "formulir ini makro". Dikoreksi. Juga menambahkan komentar untuk menjelaskan diri saya lebih baik.
Jason Williams

Dan Anda benar, contoh saya yang cepat dihancurkan adalah sampah! Tetap :-). Saya hanya mencoba memikirkan contoh cepat untuk menggambarkan ide ini: kode apa pun yang menghapus pointer harus memastikan bahwa pointer diatur ke NULL, bahkan jika orang lain (penelepon) memiliki pointer itu. Jadi selalu berikan referensi ke pointer sehingga dapat dipaksa untuk NULL pada titik penghapusan.
Jason Williams

1

"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:

delete myObj;
myobj = 0

menjadi for-liner di lingkungan multithreaded:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

"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:

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

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.


Saya gagal melihat, bagaimana 4-liner secara signifikan lebih kompleks daripada 3-liner (kita harus tetap menggunakan lock_guards) dan jika destruktor Anda melempar, Anda tetap dalam masalah.
MikeMB

Ketika saya pertama kali melihat jawaban ini, saya tidak mengerti mengapa Anda ingin null pointer di destructor, tapi sekarang saya lakukan - itu untuk kasus di mana objek yang memiliki pointer digunakan setelah dihapus!
Mark Ransom


0

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.


0

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.


0

Jika kode itu bukan bagian paling kritis kinerja aplikasi Anda, buat tetap sederhana dan gunakan shared_ptr:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.