Mengatur variabel ke NULL setelah gratis


156

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


3
itu selalu berguna untuk dapat memeriksa apakah ptr == NULLsebelum melakukan sesuatu dengannya. Jika Anda tidak membatalkan pointer free'd Anda, Anda akan mendapatkan ptr != NULLtetapi masih pointer tidak dapat digunakan.
Ki Jéy

Pointer yang menggantung dapat menyebabkan kerentanan yang dapat dieksploitasi seperti Use-After-Free .
Константин Ван

Jawaban:


285

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.


3
Saya tidak mengerti mengapa Anda akan "menginisialisasi pointer ke NULL sebelum mereka diberi nilai pointer yang benar"?
Paul Biggar

26
@ Paul: Dalam kasus khusus, deklarasi bisa dibaca 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.
Martin v. Löwis

1
Saya pribadi berpikir bahwa dalam basis kode yang tidak sepele apa pun yang mendapatkan kesalahan untuk dereferencing nol sama kaburnya dengan mendapatkan kesalahan untuk dereferencing alamat yang bukan milik Anda. Saya pribadi tidak pernah repot.
wilhelmtell

9
Wilhelm, intinya adalah bahwa dengan dereference pointer nol Anda mendapatkan crash yang menentukan dan lokasi sebenarnya dari masalah. Akses yang buruk dapat atau mungkin tidak macet, dan merusak data atau perilaku dengan cara yang tidak terduga di tempat yang tidak terduga.
Amit Naidu

4
Sebenarnya, menginisialisasi pointer ke NULL memiliki setidaknya satu kelemahan signifikan: ia dapat mencegah kompiler memperingatkan Anda tentang variabel yang tidak diinisialisasi. Kecuali logika kode Anda benar-benar secara eksplisit menangani nilai untuk pointer (mis. If (nPtr == NULL) dosomething ...) lebih baik membiarkannya apa adanya.
Eric

37

Menetapkan pointer ke NULLafter freeadalah 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 NULLafter freeseharusnya mencegah masalah "double free" yang ditakuti ketika nilai pointer yang sama diteruskan ke freelebih 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 untuk free. Tak perlu dikatakan, menetapkan pointer ke NULLsetelah freemencapai 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 ke freelebih dari satu kali. Dalam kasus seperti itu, mengatur pointer ke NULLdan 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 NULLatau tidak NULL, tidak apa-apa untuk mengatur nilai pointer ke NULLsetelah free. Tapi sebagai umum "praktik yang baik" aturan (seperti dalam "selalu mengatur pointer Anda ke NULLsetelah free") itu, sekali lagi, palsu terkenal dan cukup berguna, sering diikuti oleh beberapa untuk murni agama, voodoo-seperti alasan.


1
Pastinya. Saya tidak ingat pernah menyebabkan bebas ganda yang akan diperbaiki dengan mengatur pointer ke NULL setelah membebaskan, tetapi saya telah menyebabkan banyak yang tidak.
LnxPrgr3

4
@ ANT "meragukan" agak banyak. Itu semua tergantung pada use case. Jika nilai pointer pernah digunakan dalam arti benar / salah, itu bukan hanya praktik yang valid, itu praktik terbaik.
Coder

1
@Coder Benar-benar salah. Jika nilai pointer digunakan dalam arti palsu yang benar untuk mengetahui apakah atau tidak menunjuk ke objek sebelum panggilan untuk bebas, itu bukan hanya bukan praktik terbaik, itu salah . Sebagai contoh: foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. Di sini, pengaturan baruntuk NULLsetelah panggilan untuk freeakan menyebabkan fungsi berpikir itu tidak pernah memiliki sebuah bar dan mengembalikan nilai salah!
David Schwartz

Saya tidak berpikir manfaat utama adalah untuk melindungi terhadap bebas ganda, melainkan untuk menangkap petunjuk menggantung lebih awal dan lebih andal. Misalnya ketika membebaskan struct yang menyimpan sumber daya, pointer ke memori yang dialokasikan, menangani file, dll, karena saya membebaskan pointer memori yang terkandung dan menutup file yang terkandung, saya NULL masing-masing anggota. Kemudian jika salah satu sumber daya diakses melalui penunjuk menggantung secara tidak sengaja, program cenderung salah di sana, setiap waktu. Kalau tidak, tanpa NULLing, data yang dibebaskan mungkin belum ditimpa dan bug mungkin tidak mudah direproduksi.
jimhark

1
Saya setuju bahwa kode yang terstruktur dengan baik seharusnya tidak memungkinkan case di mana pointer diakses setelah dibebaskan atau case di mana itu dibebaskan dua kali. Tetapi di dunia nyata kode saya akan dimodifikasi dan / atau dipelihara oleh seseorang yang mungkin tidak mengenal saya dan tidak memiliki waktu dan / atau keterampilan untuk melakukan sesuatu dengan benar (karena batas waktu selalu kemarin). Karena itu saya cenderung menulis fungsi anti peluru yang tidak merusak sistem meskipun disalahgunakan.
mfloris

35

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.


1
+1 Ini sebenarnya poin yang sangat bagus. Bukan alasan tentang "bebas ganda" (yang sepenuhnya palsu), tapi ini . Saya bukan penggemar mekanik NULL-ing dari pointer setelah free, tetapi ini sebenarnya masuk akal.
AnT

Jika Anda bisa mengakses pointer setelah membebaskannya melalui pointer yang sama, itu bahkan lebih mungkin bahwa Anda akan mengakses pointer setelah membebaskan objek yang ditunjuknya melalui beberapa pointer lain. Jadi ini sama sekali tidak membantu Anda - Anda masih harus menggunakan beberapa mekanisme lain untuk memastikan Anda tidak mengakses objek melalui satu pointer setelah membebaskannya melalui yang lain. Anda mungkin juga menggunakan metode itu untuk melindungi dalam case pointer yang sama juga.
David Schwartz

1
@ Davidvidchwartz: Saya tidak setuju dengan komentar Anda. Ketika saya harus menulis setumpuk untuk latihan universitas beberapa minggu yang lalu, saya punya masalah, saya menyelidiki beberapa jam. Saya mengakses beberapa memori yang sudah bebas di beberapa titik (gratis itu beberapa baris terlalu dini). Dan terkadang itu mengarah pada perilaku yang sangat aneh. Jika saya akan mengatur pointer ke NULL setelah membebaskannya, akan ada segfault "sederhana" dan saya akan menghemat beberapa jam kerja. Jadi +1 untuk jawaban ini!
mozzbozz

2
@katze_sonne Bahkan jam yang berhenti tepat dua kali sehari. Ini jauh lebih mungkin bahwa pengaturan pointer ke NULL akan menyembunyikan bug dengan mencegah akses yang salah ke objek yang sudah dibebaskan dari segfaulting dalam kode yang memeriksa NULL dan kemudian diam-diam gagal memeriksa objek yang seharusnya diperiksa. (Mungkin menetapkan pointer ke NULL setelah bebas dalam build debug tertentu mungkin bisa membantu, atau mengaturnya ke nilai selain NULL yang dijamin untuk segfault mungkin masuk akal. Tetapi kekonyolan ini terjadi untuk membantu Anda sekali bukan argumen yang mendukungnya) .)
David Schwartz

@ DavidSchwartz Yah, itu terdengar masuk akal ... Terima kasih atas komentar Anda, saya akan mempertimbangkan ini di masa depan! :) +1
mozzbozz

20

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:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** p_tmp = (p); free(*(p_tmp)); *(p_tmp) = NULL; } while (0)
#endif

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.


3
Versi makro memiliki bug halus jika Anda menggunakannya setelah pernyataan if tanpa tanda kurung.
Mark Ransom

Ada apa dengan (void) 0? Kode ini dapat: if (x) myfree (& x); lain do_foo (); menjadi jika (x) {gratis (* (& x)); * (& x) = null; } membatalkan 0; lain do_foo (); Yang lain adalah kesalahan.
jmucchiello

Makro itu adalah tempat yang sempurna untuk operator koma: free ( (p)), * (p) = null. Tentu saja masalah selanjutnya adalah mengevaluasi * (p) dua kali. Itu harus {void * _pp = (p); gratis (* _ pp); * _pp = null; } Bukankah preprosesor itu menyenangkan.
jmucchiello

5
Makro tidak boleh dalam tanda kurung, itu harus di do { } while(0)blok sehingga if(x) myfree(x); else dostuff();tidak rusak.
Chris Lutz

3
Seperti yang dikatakan Lutz, tubuh makro do {X} while (0)adalah IMO cara terbaik untuk membuat tubuh makro yang "terasa seperti dan bekerja seperti" suatu fungsi. Kebanyakan kompiler mengoptimalkan loop.
Mike Clark

7

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.


5
Program tidak selalu macet dengan segfault. Jika cara Anda mengakses pointer berarti offset yang cukup besar diterapkan sebelum dereferencing, maka mungkin mencapai memori yang dapat dialamatkan: ((MyHugeStruct *) 0) -> fieldNearTheEnd. Dan itu bahkan sebelum Anda berurusan dengan perangkat keras yang tidak segfault pada akses 0 sama sekali. Namun, program ini lebih cenderung macet dengan segfault.
Steve Jessop

7

Mengatur pointer ke freememori '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 nPtrakan keluar dari ruang lingkup segera setelah itu nPtr = NULL, sepertinya tidak ada alasan untuk mengaturnya NULL. Namun, dalam kasus structanggota 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 NULLsetelah setiap gratis, tetapi memiliki potensi menunjukkan masalah besar.


7

bug yang paling umum di c adalah ganda gratis. Pada dasarnya kamu melakukan hal seperti itu

free(foobar);
/* lot of code */
free(foobar);

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 ini

if(foobar != null){
  free(foobar);
}

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


11
Anda sebenarnya dapat menelepon gratis () tanpa memeriksa - gratis (NULL) didefinisikan sebagai tidak melakukan apa-apa.
Amber

5
Bukankah itu menyembunyikan bug? (Seperti membebaskan terlalu banyak.)
Georg Schölly

1
thanx, saya mengerti. Saya mencoba: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 }
Shaobo Wang

5
Seperti yang saya katakan, 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 membutuhkan free(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 pointer NULL, itu tidak boleh dipaksakan pada mereka.
Chris Lutz

2
Ada beberapa hal di dunia yang memberikan kurangnya profesionalisme pada bagian dari pembuat kode C. Tetapi mereka termasuk "memeriksa pointer untuk NULL sebelum memanggil free" (bersama dengan hal-hal seperti "casting hasil dari fungsi alokasi memori" atau "menggunakan jenis nama tanpa dipikirkan dengan sizeof").
AnT

6

Gagasan di balik ini, adalah untuk menghentikan penggunaan kembali pointer yang tidak sengaja.


4

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.


Pada Windows, setidaknya, debug build akan mengatur memori ke 0xdddddddd sehingga ketika Anda menggunakan pointer ke memori yang dihapus Anda langsung tahu. Seharusnya ada mekanisme serupa di semua platform.
i_am_jorf

2
jeffamaphone, blok memori yang dihapus mungkin telah dialokasikan kembali dan ditugaskan ke objek lain pada saat Anda menggunakan pointer lagi.
Constantin

4

Dari standar ANSI C:

void free(void *ptr);

Fungsi bebas menyebabkan ruang yang ditunjuk oleh ptr untuk dialokasikan, yaitu, disediakan untuk alokasi lebih lanjut. Jika ptr adalah pointer nol, tidak ada tindakan yang terjadi. Kalau tidak, jika argumen tidak cocok dengan pointer yang sebelumnya dikembalikan oleh fungsi calloc, malloc, atau realalloc, atau jika ruang telah dibatalkan alokasi oleh panggilan untuk membebaskan atau realokasi, perilaku tidak terdefinisi.

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

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}

@DrPizza - Kesalahan (menurut saya) adalah sesuatu yang menyebabkan program Anda tidak berfungsi sebagaimana mestinya. Jika tersembunyi ganda bebas merusak program Anda, itu adalah kesalahan. Jika berfungsi persis seperti yang dimaksudkan, itu bukan kesalahan.
Chris Lutz

@DrPizza: Saya baru saja menemukan argumen mengapa orang harus mengaturnya NULLuntuk menghindari kesalahan masking. stackoverflow.com/questions/1025589/... Sepertinya dalam beberapa kasus beberapa kesalahan disembunyikan.
Georg Schölly

1
Ketahuilah bahwa void pointer-to-pointer memiliki masalah: c-faq.com/ptrs/genericpp.html
Aman

3
@ Chris, tidak, pendekatan terbaik adalah struktur kode. Jangan melempar mallocs acak dan membebaskan seluruh basis kode Anda, simpan hal-hal terkait bersama-sama. "Modul" yang mengalokasikan sumber daya (memori, file, ...) bertanggung jawab untuk membebaskannya dan harus menyediakan fungsi untuk melakukannya sehingga menjaga pointer juga. Untuk sumber daya tertentu, Anda kemudian memiliki tepat satu tempat di mana dialokasikan dan satu tempat di mana ia dilepaskan, keduanya berdekatan.
Amankan

4
@ Chris Lutz: Hogwash. Jika Anda menulis kode yang membebaskan pointer yang sama dua kali, program Anda memiliki kesalahan logis di dalamnya. Menyamarkan kesalahan logis dengan membuatnya tidak crash tidak berarti programnya benar: masih melakukan sesuatu yang tidak masuk akal. Tidak ada skenario di mana penulisan bebas ganda dibenarkan.
DrPizza

4

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.


4

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

free(ptr);
ptr = NULL;

3

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.


2

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:


if(ptr)
   ptr->CallSomeMethod();

Menandai pointer secara eksplisit sebagai NULL setelah membebaskannya memungkinkan penggunaan seperti ini di C / C ++.


5
Dalam banyak kasus, di mana penunjuk NULL tidak masuk akal, akan lebih baik untuk menulis pernyataan saja.
Erich Kitzmueller

2

Ini mungkin lebih merupakan argumen untuk menginisialisasi semua petunjuk ke NULL, tetapi sesuatu seperti ini bisa menjadi bug yang sangat licik:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

pberakhir di tempat yang sama pada tumpukan seperti yang sebelumnya nPtr, jadi mungkin masih mengandung pointer yang tampaknya valid. Menugaskan untuk *pmungkin 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 ...


2

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


2

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.


2

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.


2

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


Jawaban yang bagus. Saya ingin menambahkan satu hal. Meninjau basis data bug adalah baik untuk dilakukan karena berbagai alasan. Tetapi dalam konteks pertanyaan awal, ingatlah akan sulit untuk mengetahui berapa banyak masalah pointer yang tidak valid yang dicegah, atau setidaknya ditangkap sedini mungkin untuk tidak masuk ke dalam basis data bug. Sejarah bug memberikan bukti yang lebih baik untuk menambahkan aturan pengkodean.
jimhark

2

Ada dua alasan:

Hindari crash saat membebaskan dua kali

Ditulis oleh RageZ dalam pertanyaan rangkap .

Bug yang paling umum di c adalah double free. Pada dasarnya kamu melakukan hal seperti itu

free(foobar);
/* lot of code */
free(foobar);

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 ini

if(foobar != NULL){
  free(foobar);
}

Juga perlu dicatat itu 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 merupakan alasan utama mengapa semua bahasa dengan pengumpulan sampah (Java, dotnet) sangat bangga tidak memiliki masalah ini dan juga tidak harus pergi untuk mengembangkan manajemen memori secara keseluruhan.

Hindari menggunakan pointer yang sudah dibebaskan

Ditulis oleh Martin v. Löwis dalam jawaban lain .

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.


1

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


1

Itu selalu disarankan untuk mendeklarasikan variabel pointer dengan NULL seperti,

int *ptr = NULL;

Katakanlah, ptr menunjuk ke alamat memori 0x1000 . Setelah menggunakan free(ptr), selalu disarankan untuk membatalkan variabel pointer dengan mendeklarasikan kembali ke NULL . misalnya:

free(ptr);
ptr = NULL;

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

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.

1

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.

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.