Di perusahaan saya ada aturan pengkodean yang mengatakan, setelah membebaskan memori apa pun, setel ulang variabel ke NULL
. Sebagai contoh ...
void some_func ()
{
int *nPtr;
nPtr = malloc (100);
free (nPtr);
nPtr = NULL;
return;
}
Saya merasa bahwa, dalam kasus-kasus seperti kode yang ditunjukkan di atas, pengaturan untuk NULL
tidak memiliki arti. Atau apakah saya melewatkan sesuatu?
Jika tidak ada artinya dalam kasus seperti itu, saya akan mengambilnya dengan "tim kualitas" untuk menghapus aturan pengkodean ini. Tolong saran.
c
coding-style
malloc
free
heap-memory
Alphaneo
sumber
sumber
ptr == NULL
sebelum melakukan sesuatu dengannya. Jika Anda tidak membatalkan pointer free'd Anda, Anda akan mendapatkanptr != NULL
tetapi masih pointer tidak dapat digunakan.Jawaban:
Mengatur pointer yang tidak digunakan ke NULL adalah gaya defensif, melindungi terhadap bug pointer menjuntai. Jika pointer menggantung diakses setelah dibebaskan, Anda dapat membaca atau menimpa memori acak. Jika null pointer diakses, Anda mendapatkan crash langsung di sebagian besar sistem, memberi tahu Anda apa kesalahannya.
Untuk variabel lokal, mungkin sedikit tidak ada gunanya jika "jelas" bahwa pointer tidak diakses lagi setelah dibebaskan, jadi gaya ini lebih sesuai untuk data anggota dan variabel global. Bahkan untuk variabel lokal, mungkin pendekatan yang baik jika fungsi berlanjut setelah memori dilepaskan.
Untuk menyelesaikan gaya, Anda juga harus menginisialisasi pointer ke NULL sebelum mereka mendapatkan nilai pointer yang benar.
sumber
int *nPtr=NULL;
. Sekarang, saya setuju bahwa ini akan berlebihan, dengan malloc mengikuti tepat di baris berikutnya. Namun, jika ada kode antara deklarasi dan inisialisasi pertama, seseorang mungkin mulai menggunakan variabel meskipun belum memiliki nilai. Jika Anda inisialisasi nol, Anda mendapatkan segfault; tanpa, Anda mungkin lagi membaca atau menulis memori acak. Demikian juga, jika variabel kemudian hanya diinisialisasi dengan syarat, nanti akses yang salah akan memberi Anda crash instan jika Anda ingat untuk null-inisialisasi.Menetapkan pointer ke
NULL
afterfree
adalah praktik yang meragukan yang sering dipopulerkan sebagai aturan "pemrograman yang baik" pada premis yang keliru. Ini adalah salah satu kebenaran palsu yang termasuk dalam kategori "kedengarannya benar" tetapi pada kenyataannya tidak ada yang benar-benar berguna (dan kadang-kadang menyebabkan konsekuensi negatif).Diduga, menetapkan pointer ke
NULL
afterfree
seharusnya mencegah masalah "double free" yang ditakuti ketika nilai pointer yang sama diteruskan kefree
lebih dari satu kali. Pada kenyataannya, dalam 9 kasus dari 10 masalah "bebas ganda" nyata terjadi ketika objek pointer yang berbeda memegang nilai pointer yang sama digunakan sebagai argumen untukfree
. Tak perlu dikatakan, menetapkan pointer keNULL
setelahfree
mencapai apa-apa untuk mencegah masalah dalam kasus seperti itu.Tentu saja, dimungkinkan untuk mengalami masalah "bebas ganda" saat menggunakan objek pointer yang sama sebagai argumen
free
. Namun, dalam situasi seperti itu biasanya menunjukkan masalah dengan struktur logis umum kode, bukan sekadar "bebas ganda". Cara yang tepat untuk menangani masalah dalam kasus-kasus seperti itu adalah dengan meninjau dan memikirkan kembali struktur kode untuk menghindari situasi ketika pointer yang sama diteruskan kefree
lebih dari satu kali. Dalam kasus seperti itu, mengatur pointer keNULL
dan mempertimbangkan masalah "tetap" tidak lebih dari upaya untuk menyapu masalah di bawah karpet. Ini tidak akan berfungsi secara umum, karena masalah dengan struktur kode akan selalu menemukan cara lain untuk memanifestasikan dirinya.Akhirnya, jika kode Anda dirancang khusus untuk bergantung pada nilai pointer yang ada
NULL
atau tidakNULL
, tidak apa-apa untuk mengatur nilai pointer keNULL
setelahfree
. Tapi sebagai umum "praktik yang baik" aturan (seperti dalam "selalu mengatur pointer Anda keNULL
setelahfree
") itu, sekali lagi, palsu terkenal dan cukup berguna, sering diikuti oleh beberapa untuk murni agama, voodoo-seperti alasan.sumber
foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;
. Di sini, pengaturanbar
untukNULL
setelah panggilan untukfree
akan menyebabkan fungsi berpikir itu tidak pernah memiliki sebuah bar dan mengembalikan nilai salah!Sebagian besar respons berfokus pada mencegah bebas ganda, tetapi menetapkan pointer ke NULL memiliki manfaat lain. Setelah Anda membebaskan pointer, memori itu tersedia untuk dialokasikan kembali oleh panggilan lain ke malloc. Jika Anda masih memiliki pointer asli di sekitar Anda mungkin berakhir dengan bug di mana Anda mencoba untuk menggunakan pointer setelah membebaskan dan merusak beberapa variabel lain, dan kemudian program Anda memasuki keadaan yang tidak diketahui dan segala macam hal buruk dapat terjadi (crash jika Anda Beruntung, korupsi data jika Anda beruntung). Jika Anda telah menetapkan pointer ke NULL setelah bebas, setiap upaya untuk membaca / menulis melalui pointer itu nantinya akan menghasilkan segfault, yang umumnya lebih disukai daripada kerusakan memori acak.
Untuk kedua alasan, itu bisa menjadi ide yang baik untuk mengatur pointer ke NULL setelah gratis (). Tapi itu tidak selalu perlu. Misalnya, jika variabel pointer keluar dari ruang lingkup segera setelah bebas (), tidak ada banyak alasan untuk mengaturnya ke NULL.
sumber
free
, tetapi ini sebenarnya masuk akal.Ini dianggap praktik yang baik untuk menghindari memori yang menimpa. Dalam fungsi di atas, itu tidak perlu, tetapi seringkali ketika dilakukan dapat menemukan kesalahan aplikasi.
Coba sesuatu seperti ini sebagai gantinya:
DEBUG_VERSION memungkinkan Anda membebaskan dalam kode debug, tetapi keduanya secara fungsional sama.
Sunting : Ditambahkan do ... sementara seperti yang disarankan di bawah ini, terima kasih.
sumber
do { } while(0)
blok sehinggaif(x) myfree(x); else dostuff();
tidak rusak.do {X} while (0)
adalah IMO cara terbaik untuk membuat tubuh makro yang "terasa seperti dan bekerja seperti" suatu fungsi. Kebanyakan kompiler mengoptimalkan loop.Jika Anda mencapai pointer yang telah bebas () d, mungkin rusak atau tidak. Memori itu mungkin dialokasikan kembali ke bagian lain dari program Anda dan kemudian Anda mendapatkan kerusakan memori,
Jika Anda mengatur pointer ke NULL, maka jika Anda mengaksesnya, program selalu macet dengan segfault. Tidak ada lagi, kadang-kadang itu berfungsi '', tidak ada lagi, crash dengan cara yang tidak terduga ''. Cara ini lebih mudah untuk di-debug.
sumber
Mengatur pointer ke
free
memori 'd berarti bahwa setiap upaya untuk mengakses memori melalui pointer akan segera macet, alih-alih menyebabkan perilaku yang tidak ditentukan. Itu membuatnya lebih mudah untuk menentukan di mana kesalahan terjadi.Saya bisa melihat argumen Anda: karena
nPtr
akan keluar dari ruang lingkup segera setelah itunPtr = NULL
, sepertinya tidak ada alasan untuk mengaturnyaNULL
. Namun, dalam kasusstruct
anggota atau tempat lain di mana pointer tidak segera keluar dari ruang lingkup, itu lebih masuk akal. Tidak segera jelas apakah pointer itu akan digunakan lagi oleh kode yang seharusnya tidak menggunakannya.Kemungkinan aturan dinyatakan tanpa membuat perbedaan antara dua kasus ini, karena jauh lebih sulit untuk secara otomatis menegakkan aturan, apalagi bagi pengembang untuk mengikutinya. Tidak ada salahnya untuk menetapkan pointer
NULL
setelah setiap gratis, tetapi memiliki potensi menunjukkan masalah besar.sumber
bug yang paling umum di c adalah ganda gratis. Pada dasarnya kamu melakukan hal seperti itu
dan itu berakhir sangat buruk, OS mencoba untuk membebaskan beberapa memori yang sudah dibebaskan dan umumnya segfault. Jadi praktik yang baik adalah mengatur
NULL
, sehingga Anda dapat melakukan tes dan memeriksa apakah Anda benar-benar perlu membebaskan memori iniJuga perlu dicatat bahwa
free(NULL)
tidak akan melakukan apa pun sehingga Anda tidak perlu menulis pernyataan if. Saya bukan benar-benar seorang guru OS tetapi saya cukup bahkan sekarang sebagian besar OS akan crash pada ganda gratis.Itu juga alasan utama mengapa semua bahasa dengan pengumpulan sampah (Java, dotnet) sangat bangga tidak memiliki masalah ini dan juga tidak harus menyerahkan manajemen memori secara keseluruhan kepada pengembang.
sumber
p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
free(void *ptr)
tidak dapat mengubah nilai pointer yang dilewatkan. Itu dapat mengubah isi pointer, data yang disimpan di alamat itu , tetapi bukan alamat itu sendiri , atau nilai pointer . Itu akan membutuhkanfree(void **ptr)
(yang tampaknya tidak diizinkan oleh standar) atau makro (yang diizinkan dan sangat portabel tetapi orang tidak suka makro). Juga, C bukan tentang kenyamanan, ini tentang memberi pemrogram kontrol sebanyak yang mereka inginkan. Jika mereka tidak ingin menambahkan overhead pengaturan pointerNULL
, itu tidak boleh dipaksakan pada mereka.free
" (bersama dengan hal-hal seperti "casting hasil dari fungsi alokasi memori" atau "menggunakan jenis nama tanpa dipikirkan dengansizeof
").Gagasan di balik ini, adalah untuk menghentikan penggunaan kembali pointer yang tidak sengaja.
sumber
Ini (sebenarnya) penting. Meskipun Anda mengosongkan memori, bagian selanjutnya dari program ini dapat mengalokasikan sesuatu yang baru yang terjadi pada ruang tersebut. Pointer lama Anda sekarang akan menunjuk ke sepotong memori yang valid. Maka dimungkinkan bahwa seseorang akan menggunakan pointer, menghasilkan kondisi program yang tidak valid.
Jika Anda NULL keluar pointer, maka setiap upaya untuk menggunakannya akan merusak 0x0 dan crash di sana, yang mudah untuk debug. Pointer acak yang menunjuk ke memori acak sulit di-debug. Jelas itu tidak perlu, tetapi karena itu ada dalam dokumen praktik terbaik.
sumber
Dari standar ANSI C:
"perilaku tidak terdefinisi" hampir selalu merupakan program crash. Untuk menghindari ini, aman untuk mereset pointer ke NULL. free () sendiri tidak dapat melakukan ini karena hanya diteruskan dengan pointer, bukan pointer ke pointer. Anda juga dapat menulis versi bebas yang lebih aman () yang NULLs the pointer:
sumber
NULL
untuk menghindari kesalahan masking. stackoverflow.com/questions/1025589/... Sepertinya dalam beberapa kasus beberapa kesalahan disembunyikan.Saya menemukan ini sedikit membantu seperti dalam pengalaman saya ketika orang mengakses alokasi memori yang dibebaskan itu hampir selalu karena mereka memiliki pointer lain ke suatu tempat. Dan kemudian itu bertentangan dengan standar pengkodean pribadi lain yang "Hindari kekacauan tidak berguna", jadi saya tidak melakukannya karena saya pikir itu jarang membantu dan membuat kode sedikit kurang dapat dibaca.
Namun - saya tidak akan mengatur variabel ke nol jika pointer tidak seharusnya digunakan lagi, tetapi seringkali desain tingkat yang lebih tinggi memberi saya alasan untuk mengaturnya ke nol. Sebagai contoh jika pointer adalah anggota kelas dan saya telah menghapus apa yang menunjuk ke maka "kontrak" jika Anda suka kelas adalah bahwa anggota tersebut akan menunjuk ke sesuatu yang valid setiap saat sehingga harus diatur ke nol untuk alasan itu. Perbedaan kecil tapi saya pikir yang penting.
Dalam c ++ penting untuk selalu berpikir siapa yang memiliki data ini ketika Anda mengalokasikan sebagian memori (kecuali jika Anda menggunakan smart pointer tetapi itupun diperlukan pemikiran). Dan proses ini cenderung mengarah ke pointer yang secara umum menjadi anggota beberapa kelas dan umumnya Anda ingin kelas berada dalam keadaan yang valid setiap saat, dan cara termudah untuk melakukannya adalah dengan mengatur variabel anggota ke NULL untuk menunjukkannya menunjuk untuk apa-apa sekarang.
Pola umum adalah mengatur semua pointer anggota ke NULL dalam konstruktor dan menghapus panggilan destruktor pada pointer apa pun ke data yang menurut desain Anda dimiliki oleh kelas . Jelas dalam hal ini Anda harus mengatur pointer ke NULL ketika Anda menghapus sesuatu untuk menunjukkan bahwa Anda tidak memiliki data apa pun sebelumnya.
Jadi untuk meringkas, ya saya sering mengatur pointer ke NULL setelah menghapus sesuatu, tetapi itu sebagai bagian dari desain yang lebih besar dan pemikiran tentang siapa yang memiliki data daripada karena secara membabi buta mengikuti aturan standar pengkodean. Saya tidak akan melakukannya dalam contoh Anda karena saya pikir tidak ada manfaat untuk melakukannya dan itu menambahkan "kekacauan" yang dalam pengalaman saya sama bertanggung jawab atas bug dan kode buruk seperti hal semacam ini.
sumber
Baru-baru ini saya menemukan pertanyaan yang sama setelah saya mencari jawabannya. Saya mencapai kesimpulan ini:
Ini adalah praktik terbaik, dan seseorang harus mengikuti ini untuk membuatnya portabel pada semua sistem (tertanam).
free()
adalah fungsi pustaka, yang bervariasi ketika seseorang mengubah platform, jadi Anda seharusnya tidak berharap bahwa setelah melewati pointer ke fungsi ini dan setelah membebaskan memori, pointer ini akan diatur ke NULL. Ini mungkin tidak berlaku untuk beberapa perpustakaan yang diterapkan untuk platform.jadi selalu cocok
sumber
Aturan ini berguna saat Anda mencoba menghindari skenario berikut:
1) Anda memiliki fungsi yang sangat panjang dengan logika dan manajemen memori yang rumit dan Anda tidak ingin secara tidak sengaja menggunakan kembali pointer ke memori yang dihapus nanti dalam fungsi tersebut.
2) Pointer adalah variabel anggota kelas yang memiliki perilaku yang cukup kompleks dan Anda tidak ingin secara tidak sengaja menggunakan kembali pointer ke memori yang dihapus di fungsi lain.
Dalam skenario Anda, itu tidak masuk akal, tetapi jika fungsinya lebih lama, mungkin penting.
Anda mungkin berpendapat bahwa menyetelnya ke NULL mungkin sebenarnya menutupi kesalahan logika nanti, atau dalam kasus di mana Anda menganggap itu sah, Anda masih crash pada NULL, jadi itu tidak masalah.
Secara umum, saya akan menyarankan Anda untuk mengaturnya ke NULL ketika Anda berpikir itu adalah ide yang baik, dan tidak repot-repot ketika Anda berpikir itu tidak layak. Alih-alih fokus pada menulis fungsi pendek dan kelas yang dirancang dengan baik.
sumber
Untuk menambahkan apa yang dikatakan orang lain, salah satu metode penggunaan pointer yang baik adalah selalu memeriksa apakah itu pointer yang valid atau tidak. Sesuatu seperti:
Menandai pointer secara eksplisit sebagai NULL setelah membebaskannya memungkinkan penggunaan seperti ini di C / C ++.
sumber
Ini mungkin lebih merupakan argumen untuk menginisialisasi semua petunjuk ke NULL, tetapi sesuatu seperti ini bisa menjadi bug yang sangat licik:
p
berakhir di tempat yang sama pada tumpukan seperti yang sebelumnyanPtr
, jadi mungkin masih mengandung pointer yang tampaknya valid. Menugaskan untuk*p
mungkin menimpa semua jenis hal yang tidak terkait dan menyebabkan bug jelek. Terutama jika kompilator menginisialisasi variabel lokal dengan nol dalam mode debug tetapi tidak setelah optimasi dihidupkan. Jadi build debug tidak menunjukkan tanda-tanda bug saat rilis build meledak secara acak ...sumber
Mengatur pointer yang baru saja dibebaskan ke NULL tidak wajib tetapi merupakan praktik yang baik. Dengan cara ini, Anda dapat menghindari 1) menggunakan runcing bebas 2) membebaskannya
sumber
Pengaturan pointer ke NULL adalah untuk melindungi terhadap apa yang disebut dengan double-free - sebuah situasi ketika free () dipanggil lebih dari sekali untuk alamat yang sama tanpa mengalokasikan kembali blok di alamat itu.
Double-mengarah bebas ke perilaku tidak terdefinisi - biasanya menumpuk korupsi atau segera crash program. Memanggil bebas () untuk pointer NULL tidak melakukan apa-apa dan karenanya dijamin aman.
Jadi praktik terbaik kecuali jika Anda sekarang yakin bahwa pointer meninggalkan ruang lingkup segera atau segera setelah bebas () adalah untuk menetapkan pointer itu ke NULL sehingga bahkan jika gratis () dipanggil lagi itu sekarang dipanggil untuk pointer NULL dan perilaku tidak terdefinisi dihindari.
sumber
Idenya adalah bahwa jika Anda mencoba melakukan dereferensi penunjuk yang tidak lagi valid setelah membebaskannya, Anda ingin gagal dengan keras (segfault) daripada diam-diam dan misterius.
Tetapi berhati-hatilah. Tidak semua sistem menyebabkan segfault jika Anda melakukan referensi NULL. Aktif (setidaknya beberapa versi) AIX, * (int *) 0 == 0, dan Solaris memiliki kompatibilitas opsional dengan "fitur" AIX ini.
sumber
Untuk pertanyaan awal: Mengatur penunjuk ke NULL secara langsung setelah membebaskan konten adalah buang-buang waktu, asalkan kode memenuhi semua persyaratan, sepenuhnya debugged dan tidak akan pernah dimodifikasi lagi. Di sisi lain, defensif NULLing pointer yang telah dibebaskan bisa sangat berguna ketika seseorang tanpa berpikir menambahkan blok kode baru di bawah bebas (), ketika desain modul asli tidak benar, dan dalam kasus itu -compiles-but-don't-do-what-I-want.
Dalam sistem apa pun, ada tujuan yang tidak dapat dicapai untuk membuatnya termudah untuk hal yang benar, dan biaya yang tidak dapat dikurangi dari pengukuran yang tidak akurat. Di C kami ditawari satu set alat yang sangat tajam, sangat kuat, yang dapat menciptakan banyak hal di tangan pekerja terampil, dan menimbulkan segala macam cedera metaforis bila ditangani dengan tidak benar. Beberapa sulit dimengerti atau digunakan dengan benar. Dan orang-orang, yang secara alami menolak risiko, melakukan hal-hal yang tidak rasional seperti memeriksa sebuah pointer untuk nilai NULL sebelum menelepon gratis dengannya ...
Masalah pengukurannya adalah bahwa setiap kali Anda mencoba untuk membagi yang baik dari yang kurang baik, semakin kompleks kasusnya, semakin besar kemungkinan Anda mendapatkan pengukuran yang ambigu. Jika tujuannya hanya menjaga praktik yang baik, maka beberapa yang ambigu akan dibuang dengan yang sebenarnya tidak baik. JIKA tujuan Anda adalah untuk menghilangkan hal-hal yang tidak baik, maka ambiguitas akan tetap ada pada yang baik. Dua gol, tetap hanya baik atau menghilangkan yang jelas-jelas buruk, tampaknya akan ditentang secara diametris, tetapi biasanya ada kelompok ketiga yang tidak satu atau yang lain, beberapa dari keduanya.
Sebelum Anda membuat kasus dengan departemen kualitas, coba lihat melalui basis data bug untuk melihat seberapa sering, jika pernah, nilai pointer yang tidak valid menyebabkan masalah yang harus ditulis. Jika Anda ingin membuat perbedaan nyata, identifikasi masalah yang paling umum dalam kode produksi Anda dan usulkan tiga cara untuk mencegahnya
sumber
Ada dua alasan:
Hindari crash saat membebaskan dua kali
Ditulis oleh RageZ dalam pertanyaan rangkap .
Hindari menggunakan pointer yang sudah dibebaskan
Ditulis oleh Martin v. Löwis dalam jawaban lain .
sumber
Karena Anda memiliki tim jaminan kualitas, izinkan saya menambahkan poin kecil tentang QA. Beberapa alat QA otomatis untuk C akan menandai penugasan ke pointer yang dibebaskan sebagai "penugasan tidak berguna
ptr
". Misalnya PC-lint / FlexeLint dari Gimpel Software berkatatst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used
Ada beberapa cara untuk secara selektif menekan pesan, sehingga Anda masih dapat memenuhi kedua persyaratan QA, jika tim Anda memutuskan demikian.
sumber
Itu selalu disarankan untuk mendeklarasikan variabel pointer dengan NULL seperti,
Katakanlah, ptr menunjuk ke alamat memori 0x1000 . Setelah menggunakan
free(ptr)
, selalu disarankan untuk membatalkan variabel pointer dengan mendeklarasikan kembali ke NULL . misalnya:Jika tidak dideklarasikan ulang ke NULL , variabel pointer masih terus menunjuk ke alamat yang sama ( 0x1000 ), variabel pointer ini disebut pointer menggantung . Jika Anda mendefinisikan variabel pointer lain (katakanlah, q ) dan secara dinamis mengalokasikan alamat ke pointer baru, ada kemungkinan mengambil alamat yang sama ( 0x1000 ) oleh variabel pointer baru. Jika dalam kasus, Anda menggunakan pointer yang sama ( ptr ) dan memperbarui nilai di alamat yang ditunjuk oleh pointer yang sama ( ptr ), maka program akan berakhir menulis nilai ke tempat di mana q menunjuk (karena p dan q adalah menunjuk ke alamat yang sama (0x1000 )).
misalnya
sumber
Singkat cerita: Anda tidak ingin secara tidak sengaja (tidak sengaja) mengakses alamat yang telah Anda bebaskan. Karena, ketika Anda membebaskan alamat, Anda mengizinkan alamat itu di heap untuk dialokasikan ke beberapa aplikasi lain.
Namun, jika Anda tidak menetapkan pointer ke NULL, dan secara tidak sengaja mencoba untuk mendefinisiasikan pointer, atau mengubah nilai alamat itu; ANDA BISA MASIH MELAKUKANNYA. TAPI TIDAK SESUATU YANG AKAN ANDA INGIN LAKUKAN.
Mengapa saya masih dapat mengakses lokasi memori yang telah saya bebaskan? Karena: Anda mungkin telah membebaskan memori, tetapi variabel pointer masih memiliki informasi tentang alamat memori tumpukan. Jadi, sebagai strategi defensif, harap setel ke NULL.
sumber