Pedoman Inti C ++ memiliki aturan ES.20: Selalu menginisialisasi objek .
Hindari kesalahan yang digunakan sebelum ditetapkan dan perilaku terkait yang tidak terdefinisi. Hindari masalah dengan pemahaman inisialisasi yang kompleks. Sederhanakan refactoring.
Tetapi aturan ini tidak membantu menemukan bug, itu hanya menyembunyikan mereka.
Misalkan suatu program memiliki jalur eksekusi di mana ia menggunakan variabel yang tidak diinisialisasi. Itu adalah bug. Selain perilaku yang tidak terdefinisi, itu juga berarti ada yang tidak beres, dan program mungkin tidak memenuhi persyaratan produknya. Ketika akan digunakan untuk produksi, mungkin ada kerugian uang, atau bahkan lebih buruk.
Bagaimana kita menyaring bug? Kami menulis tes. Tetapi tes tidak mencakup 100% jalur eksekusi, dan tes tidak pernah mencakup 100% dari input program. Lebih dari itu, bahkan tes mencakup jalur eksekusi yang salah - itu masih bisa berlalu. Lagipula itu adalah perilaku yang tidak terdefinisi, variabel yang tidak diinisialisasi dapat memiliki nilai yang agak valid.
Tetapi selain tes kami, kami memiliki kompiler yang dapat menulis sesuatu seperti 0xCDCDCDCD ke variabel yang tidak diinisialisasi. Ini sedikit meningkatkan tingkat deteksi tes.
Bahkan lebih baik - ada alat seperti Address Sanitizer, yang akan menangkap semua pembacaan byte memori yang tidak diinisialisasi.
Dan akhirnya ada analisa statis, yang dapat melihat program dan mengatakan bahwa ada read-before-set pada jalur eksekusi itu.
Jadi kita memiliki banyak alat yang kuat, tetapi jika kita menginisialisasi variabel - pembersih tidak menemukan apa pun .
int bytes_read = 0;
my_read(buffer, &bytes_read); // err_t my_read(buffer_t, int*);
// bytes_read is not changed on read error.
// It's a bug of "my_read", but detection is suppressed by initialization.
buffer.shrink(bytes_read); // Uninitialized bytes_read could be detected here.
// Another bug: use empty buffer after read error.
use(buffer);
Ada aturan lain - jika eksekusi program menemukan bug, program harus mati sesegera mungkin. Tidak perlu tetap hidup, hanya crash, menulis crashdump, berikan kepada insinyur untuk diselidiki.
Inisialisasi variabel tidak perlu sebaliknya - program tetap hidup, ketika itu akan mendapatkan kesalahan segmentasi sebaliknya.
bytes_read
tidak diubah (jadi dijaga nol), mengapa ini dianggap bug? Program ini masih bisa berlanjut secara waras selama tidak secara implisit mengharapkannyabytes_read!=0
setelah itu. Jadi baik-baik saja pembersih tidak mengeluh. Di sisi lain, ketikabytes_read
tidak diinisialisasi sebelumnya, program tidak akan dapat melanjutkan dengan cara yang waras, jadi tidak menginisialisasibytes_read
sebenarnya memperkenalkan bug yang tidak ada sebelumnya.\0
itu buggy. Jika didokumentasikan tidak berurusan dengan itu, kode panggilan Anda bermasalah. Jika Anda memperbaiki kode panggilan Anda untuk memeriksabytes_read==0
sebelum menelepon digunakan, maka Anda kembali ke tempat Anda mulai: kode Anda bermasalah jika Anda tidak menginisialisasibytes_read
, aman jika Anda melakukannya. ( Biasanya fungsi seharusnya mengisi parameternya meskipun terjadi kesalahan : tidak juga. Seringkali output dibiarkan sendiri atau tidak ditentukan.)err_t
dikembalikan olehmy_read()
? Jika ada bug di mana saja dalam contoh, itu saja.Jawaban:
Alasan Anda salah pada beberapa akun:
bytes_read
memiliki nilai10
karena memiliki nilai0xcdcdcdcd
.Gagasan di balik panduan untuk selalu menginisialisasi variabel adalah untuk mengaktifkan kedua situasi ini
Variabel berisi nilai berguna sejak awal keberadaannya. Jika Anda menggabungkannya dengan panduan untuk mendeklarasikan variabel hanya sekali Anda membutuhkannya, Anda dapat menghindari programer pemeliharaan di masa depan yang terjebak dalam perangkap untuk mulai menggunakan variabel antara deklarasi dan penugasan pertama, di mana variabel akan ada tetapi tidak diinisialisasi.
Variabel berisi nilai yang ditentukan yang dapat Anda uji untuk nanti, untuk mengetahui apakah fungsi seperti
my_read
telah memperbarui nilai. Tanpa inisialisasi, Anda tidak dapat mengetahui apakahbytes_read
benar-benar memiliki nilai yang valid, karena Anda tidak dapat mengetahui nilai apa yang dimulai.sumber
= 0;
. Maksud dari saran tersebut adalah mendeklarasikan variabel pada titik di mana Anda akan memiliki nilai yang berguna untuknya, dan segera berikan nilai ini. Ini dibuat jelas secara eksplisit dalam aturan berikut ES21 dan ES22. Ketiganya harus dipahami sebagai kerja sama; bukan sebagai aturan individu yang tidak terkait.Anda menulis "aturan ini tidak membantu menemukan bug, itu hanya menyembunyikan mereka" - yah, tujuan aturan ini bukan untuk membantu menemukan bug, tetapi untuk menghindari . Dan ketika bug dihindari, tidak ada yang disembunyikan.
Mari kita bahas masalah ini dengan contoh Anda: anggap
my_read
fungsi memiliki kontrak tertulis untuk diinisialisasibytes_read
dalam semua keadaan, tetapi tidak dalam kasus kesalahan, jadi itu salah, setidaknya, untuk kasus ini. Niat Anda adalah menggunakan lingkungan run time untuk menunjukkan bug itu dengan tidak menginisialisasibytes_read
parameter terlebih dahulu. Selama Anda tahu pasti ada pembersih alamat di sana, itu memang cara yang mungkin untuk mendeteksi bug tersebut. Untuk memperbaiki bug, seseorang harus mengubahmy_read
fungsi secara internal.Tetapi ada sudut pandang yang berbeda, yang setidaknya sama-sama valid: perilaku yang salah hanya muncul dari kombinasi tidak menginisialisasi
bytes_read
sebelumnya, dan memanggilmy_read
sesudahnya (dengan harapanbytes_read
diinisialisasi setelah itu). Ini adalah situasi yang akan sering terjadi dalam komponen dunia nyata ketika spesifikasi tertulis untuk fungsi sepertimy_read
tidak 100% jelas, atau bahkan salah tentang perilaku jika terjadi kesalahan. Namun, selamabytes_read
diinisialisasi ke nol sebelum panggilan, program berperilaku sama seperti jika inisialisasi dilakukan di dalammy_read
, sehingga berperilaku dengan benar, dalam kombinasi ini tidak ada bug dalam program.Jadi rekomendasi saya yang mengikuti dari itu adalah: gunakan pendekatan non-inisialisasi hanya jika
Ini adalah kondisi yang biasanya dapat Anda atur dalam kode uji , untuk lingkungan perkakas tertentu.
Dalam kode produksi, bagaimanapun, lebih baik selalu menginisialisasi variabel seperti itu sebelumnya, itu adalah pendekatan yang lebih defensif, yang mencegah bug jika kontrak tidak lengkap atau salah, atau dalam hal pembersih alamat atau langkah-langkah keamanan serupa tidak diaktifkan. Dan aturan "crash-early" berlaku, seperti yang Anda tulis dengan benar, jika eksekusi program menemukan bug. Tetapi ketika menginisialisasi variabel sebelumnya berarti tidak ada yang salah, maka tidak perlu menghentikan eksekusi lebih lanjut.
sumber
Selalu inisialisasi variabel Anda
Perbedaan antara situasi yang Anda pertimbangkan adalah bahwa case tanpa inisialisasi menghasilkan perilaku yang tidak terdefinisi , sedangkan case di mana Anda meluangkan waktu untuk menginisialisasi menciptakan bug yang terdefinisi dengan baik dan deterministik . Saya tidak bisa menekankan betapa sangat berbeda kedua kasus ini.
Pertimbangkan contoh hipotetis yang mungkin terjadi pada karyawan hipotetis pada program simulasi hipotetis. Tim hipotetis ini secara hipotesis mencoba membuat simulasi deterministik untuk menunjukkan bahwa produk yang mereka jual memenuhi kebutuhan secara hipotesis.
Oke, saya akan berhenti dengan kata suntikan. Saya pikir Anda mendapatkan intinya ;-)
Dalam simulasi ini, ada ratusan variabel tidak diinisialisasi. Salah satu pengembang menjalankan valgrind pada simulasi dan melihat ada beberapa kesalahan "branch on uninitialized value". "Hmm, sepertinya itu bisa menyebabkan non-determinisme, membuatnya sulit untuk mengulang tes ketika kita paling membutuhkannya." Pengembang pergi ke manajemen, tetapi manajemen berada pada jadwal yang sangat ketat, dan tidak dapat menyisihkan sumber daya untuk melacak masalah ini. "Kami akhirnya menginisialisasi semua variabel kami sebelum menggunakannya. Kami memiliki praktik pengkodean yang baik."
Beberapa bulan sebelum pengiriman akhir, ketika simulasi dalam mode churn penuh, dan seluruh tim berlari untuk menyelesaikan semua hal yang dijanjikan manajemen dengan anggaran yang, seperti setiap proyek yang pernah didanai, terlalu kecil. Seseorang memperhatikan bahwa mereka tidak dapat menguji fitur penting karena, untuk beberapa alasan, sim deterministik tidak berperilaku deterministik untuk debug.
Seluruh tim mungkin telah dihentikan dan menghabiskan bagian yang lebih baik dari 2 bulan menyisir seluruh basis kode simulasi untuk memperbaiki kesalahan nilai yang tidak diinisialisasi alih-alih menerapkan dan menguji fitur. Tak perlu dikatakan, karyawan melewatkan "Sudah saya katakan" dan langsung membantu pengembang lain memahami apa nilai yang tidak diinisialisasi. Anehnya, standar pengkodean diubah tak lama setelah kejadian ini, mendorong pengembang untuk selalu menginisialisasi variabel mereka.
Dan ini adalah tembakan peringatan. Ini adalah peluru yang menyerempet hidung Anda. Masalah yang sebenarnya jauh jauh lebih berbahaya dari yang Anda bayangkan.
Menggunakan nilai yang tidak diinisialisasi adalah "perilaku tidak terdefinisi" (kecuali untuk beberapa kasus sudut seperti
char
). Perilaku tidak terdefinisi (atau UB singkatnya) sangat gila dan sangat buruk bagi Anda, sehingga Anda tidak akan pernah percaya itu lebih baik daripada alternatifnya. Terkadang Anda dapat mengidentifikasi bahwa kompiler khusus Anda mendefinisikan UB, dan kemudian aman untuk digunakan, tetapi sebaliknya, perilaku tidak terdefinisi adalah "perilaku apa pun yang dirasakan oleh kompiler." Ini dapat melakukan sesuatu yang Anda sebut "waras" seperti memiliki nilai yang tidak ditentukan. Mungkin memancarkan opcodes yang tidak valid, berpotensi menyebabkan program Anda rusak sendiri. Ini dapat memicu peringatan pada waktu kompilasi, atau kompilator bahkan dapat menganggapnya sebagai kesalahan.Atau mungkin tidak melakukan apa-apa sama sekali
Kenari saya di tambang batu bara untuk UB adalah kasing dari mesin SQL yang saya baca. Maafkan saya karena tidak menautkannya, saya gagal menemukan artikel itu lagi. Ada masalah buffer overrun di mesin SQL ketika Anda memberikan ukuran buffer yang lebih besar ke suatu fungsi, tetapi hanya pada versi tertentu dari Debian. Bug itu patuh masuk, dan dieksplorasi. Bagian yang lucu adalah: buffer overrun diperiksa . Ada kode untuk menangani buffer overrun di tempatnya. Itu terlihat seperti ini:
Saya telah menambahkan lebih banyak komentar di rendisi saya, tetapi idenya sama. Jika
put + dataLength
membungkus, itu akan lebih kecil dariput
pointer (mereka telah mengkompilasi cek waktu untuk memastikan int unsigned adalah ukuran pointer, untuk yang penasaran). Jika ini terjadi, kita tahu algoritma buffer cincin standar mungkin bingung oleh luapan ini, jadi kita mengembalikan 0. Atau apakah kita?Ternyata, overflow pada pointer tidak terdefinisi dalam C ++. Karena kebanyakan kompiler memperlakukan pointer sebagai integer, kita berakhir dengan perilaku integer overflow yang khas, yang merupakan perilaku yang kita inginkan. Namun, ini adalah perilaku yang tidak terdefinisi, artinya kompiler diizinkan untuk melakukan apa pun yang diinginkannya.
Dalam hal bug ini, Debian kebetulan memilih untuk menggunakan versi gcc baru yang tidak diperbarui oleh rasa Linux utama lainnya dalam rilis produksinya. Versi baru gcc ini memiliki pengoptimal kode mati yang lebih agresif. Kompilator melihat perilaku yang tidak terdefinisi, dan memutuskan bahwa hasil dari
if
pernyataan tersebut adalah "apa pun yang membuat optimalisasi kode," yang merupakan terjemahan UB yang benar-benar legal. Oleh karena itu, ia membuat asumsi bahwa karenaptr+dataLength
tidak akan pernah bisa di bawahptr
tanpa pointer UB kelebihan,if
pernyataan itu tidak akan pernah memicu, dan mengoptimalkan cek buffer overrun.Penggunaan "waras" UB sebenarnya menyebabkan produk SQL utama untuk mengeksploitasi buffer overrun yang harus dihindari oleh kode tertulis!
Jangan pernah mengandalkan perilaku yang tidak terdefinisi. Pernah.
sumber
bool
adalah contoh yang sangat baik di mana ada masalah yang jelas, tetapi mereka muncul di tempat lain kecuali Anda menganggap Anda sedang bekerja pada platform yang sangat membantu seperti x86 atau ARM atau MIPS di mana semua masalah ini dapat diselesaikan pada waktu opcode.switch
kurang dari 8, karena ukuran aritmatika bilangan bulat, sehingga mereka dapat menggunakan instruksi cepat yang dianggap tidak ada risiko nilai "besar" masuk. Tiba-tiba sebuah nilai yang tidak ditentukan (yang tidak akan pernah bisa dibangun menggunakan aturan kompiler) muncul, melakukan sesuatu yang tidak terduga, dan tiba-tiba Anda memiliki lompatan besar dari ujung tabel lompatan. Mengizinkan hasil yang tidak ditentukan di sini berarti setiap pernyataan switch dalam program harus memiliki jebakan ekstra untuk mendukung kasus-kasus ini yang "tidak pernah terjadi."Saya kebanyakan bekerja dalam bahasa pemrograman fungsional di mana Anda tidak diizinkan untuk menetapkan kembali variabel. Pernah. Itu sepenuhnya menghilangkan kelas bug ini. Ini tampaknya seperti pembatasan besar pada awalnya, tetapi memaksa Anda untuk menyusun kode Anda dengan cara yang konsisten dengan urutan Anda mempelajari data baru, yang cenderung menyederhanakan kode Anda dan membuatnya lebih mudah untuk dipelihara.
Kebiasaan itu juga bisa dibawa ke bahasa-bahasa yang sangat penting. Hampir selalu mungkin untuk memperbaiki kode Anda untuk menghindari menginisialisasi variabel dengan nilai dummy. Itulah yang diperintahkan oleh panduan tersebut untuk Anda lakukan. Mereka ingin Anda meletakkan sesuatu yang bermakna di sana, bukan sesuatu yang hanya akan membuat alat otomatis bahagia.
Contoh Anda dengan API gaya-C sedikit lebih rumit. Dalam kasus tersebut, ketika saya menggunakan fungsi saya akan menginisialisasi ke nol untuk menjaga kompiler dari mengeluh, tetapi suatu kali dalam
my_read
unit test, saya akan menginisialisasi ke sesuatu yang lain untuk memastikan kondisi kesalahan berfungsi dengan baik. Anda tidak perlu menguji setiap kondisi kesalahan yang mungkin terjadi pada setiap penggunaan.sumber
Tidak, itu tidak menyembunyikan bug. Alih-alih itu membuat perilaku menjadi deterministik sedemikian rupa sehingga jika pengguna menemukan kesalahan, pengembang dapat memperbanyaknya.
sumber
TL; DR: Ada dua cara untuk membuat program ini benar, menginisialisasi variabel Anda dan berdoa. Hanya satu yang memberikan hasil secara konsisten.
Sebelum saya dapat menjawab pertanyaan Anda, saya harus terlebih dahulu menjelaskan apa artinya Perilaku Tidak Terdefinisi . Sebenarnya, saya akan membiarkan penulis kompiler melakukan sebagian besar pekerjaan:
Jika Anda tidak mau membaca artikel itu, TL; DR adalah:
Pola dasar "Iblis terbang dari hidung Anda" telah gagal menyampaikan implikasi dari fakta ini, sayangnya. Meskipun dimaksudkan untuk membuktikan bahwa apa pun bisa terjadi, itu benar-benar tidak dapat dipercaya sehingga sebagian besar diabaikan.
Yang benar, bagaimanapun, adalah bahwa Perilaku Tidak Terdefinisi mempengaruhi kompilasi itu sendiri, jauh sebelum Anda bahkan mencoba untuk menggunakan program (diinstrumentasi atau tidak, dalam debugger atau tidak) dan benar-benar dapat mengubah perilakunya.
Saya menemukan contoh pada bagian 2 di atas yang mencolok:
ditransformasikan menjadi:
karena sudah jelas itu
P
tidak bisa0
karena sudah dereferensi sebelum diperiksa.Bagaimana ini berlaku untuk contoh Anda?
Nah, Anda telah membuat kesalahan umum dengan mengasumsikan bahwa Perilaku Tidak Terdefinisi akan menyebabkan kesalahan run-time. Mungkin tidak.
Mari kita bayangkan bahwa definisi
my_read
adalah:dan melanjutkan seperti yang diharapkan dari kompiler yang baik dengan inlining:
Kemudian, seperti yang diharapkan dari kompiler yang baik, kami mengoptimalkan cabang yang tidak berguna:
bytes_read
akan digunakan tanpa diinisialisasi jikaresult
tidak0
result
tidak akan pernah terjadi0
!Jadi
result
tidak pernah0
:Oh,
result
tidak pernah digunakan:Oh, kami dapat menunda deklarasi
bytes_read
:Dan di sinilah kita, sebuah transformasi yang mengkonfirmasikan yang asli, dan tidak ada debugger yang akan menjebak variabel yang tidak diinisialisasi karena tidak ada.
Saya sudah di jalan itu, memahami masalah ketika perilaku yang diharapkan dan pertemuan tidak cocok benar-benar tidak menyenangkan.
sumber
Mari kita lihat lebih dekat kode contoh Anda:
Ini adalah contoh yang bagus. Jika kami mengantisipasi kesalahan seperti ini, kami dapat menyisipkan baris
assert(bytes_read > 0);
dan menangkap bug ini saat runtime, yang tidak mungkin dilakukan dengan variabel yang tidak diinisialisasi.Tapi misalkan kita tidak melakukannya, dan kami menemukan kesalahan di dalam fungsi
use(buffer)
. Kami memuat program di debugger, memeriksa backtrace, dan mengetahui bahwa itu dipanggil dari kode ini. Jadi kami meletakkan breakpoint di bagian atas cuplikan ini, jalankan lagi, dan mereproduksi bug. Kami satu langkah mencoba menangkapnya.Jika belum diinisialisasi
bytes_read
, ini berisi sampah. Itu tidak selalu mengandung sampah yang sama setiap kali. Kami melewati garismy_read(buffer, &bytes_read);
. Sekarang, jika nilainya berbeda dari sebelumnya, kami mungkin tidak dapat mereproduksi bug sama sekali! Mungkin berhasil di waktu berikutnya, pada input yang sama, secara tidak sengaja. Jika konsisten nol, kita mendapatkan perilaku yang konsisten.Kami memeriksa nilainya, bahkan mungkin pada backtrace dalam menjalankan yang sama. Jika nol, kita dapat melihat bahwa ada sesuatu yang salah;
bytes_read
tidak boleh nol pada kesuksesan. (Atau jika bisa, kita mungkin ingin menginisialisasi ke -1.) Kita mungkin dapat menangkap bug di sini. Namun, jikabytes_read
nilai yang masuk akal itu kebetulan salah, akankah kita melihatnya secara sekilas?Hal ini terutama berlaku untuk pointer: pointer NULL akan selalu jelas dalam debugger, dapat diuji dengan sangat mudah, dan harus segfault pada perangkat keras modern jika kita mencoba untuk menghindarinya. Penunjuk sampah dapat menyebabkan bug kerusakan memori yang tidak dapat diproduksi lagi nanti, dan ini hampir tidak mungkin untuk di-debug.
sumber
OP tidak mengandalkan perilaku yang tidak terdefinisi, atau setidaknya tidak persis. Memang, mengandalkan perilaku tidak terdefinisi itu buruk. Pada saat yang sama, perilaku suatu program dalam kasus yang tidak terduga juga tidak terdefinisi, tetapi jenis yang berbeda. Jika Anda menetapkan variabel ke nol, tetapi Anda tidak berniat untuk memiliki jalur eksekusi yang menggunakan bahwa nol awal, akan Program berperilaku Anda secara masuk akal ketika Anda memiliki bug dan lakukan memiliki jalan seperti itu? Anda sekarang berada di gulma; Anda tidak berencana untuk menggunakan nilai itu, tetapi Anda tetap menggunakannya. Mungkin itu tidak berbahaya, atau mungkin akan menyebabkan program macet, atau mungkin akan menyebabkan program merusak data secara diam-diam. Kamu tidak tahu.
Apa yang OP katakan adalah bahwa ada alat yang akan membantu Anda menemukan bug ini, jika Anda membiarkannya. Jika Anda tidak menginisialisasi nilainya, tetapi kemudian Anda tetap menggunakannya, ada analisis statis dan dinamis yang akan memberi tahu Anda bahwa Anda memiliki bug. Penganalisa statis akan memberi tahu Anda bahkan sebelum Anda mulai menguji program. Sebaliknya, jika Anda secara buta menginisialisasi nilainya, para analis tidak dapat mengatakan bahwa Anda tidak berencana untuk menggunakan nilai awal itu, sehingga bug Anda tidak terdeteksi. Jika Anda beruntung itu tidak berbahaya atau hanya crash program; jika Anda beruntung itu diam-diam merusak data.
Satu-satunya tempat saya tidak setuju dengan OP adalah di bagian paling akhir, di mana ia mengatakan "ketika itu akan mendapatkan kesalahan segmentasi sebaliknya." Memang, variabel yang tidak diinisialisasi tidak akan menghasilkan kesalahan segmentasi dengan andal. Sebaliknya, saya akan mengatakan bahwa Anda harus menggunakan alat analisis statis yang tidak akan membiarkan Anda sampai pada titik berusaha untuk menjalankan program.
sumber
Sebuah jawaban untuk pertanyaan Anda perlu dipecah menjadi berbagai jenis variabel yang muncul di dalam suatu program:
Variabel lokal
Biasanya deklarasi harus tepat di tempat variabel pertama mendapatkan nilainya. Jangan predeclare variabel seperti di gaya lama C:
Ini menghilangkan 99% dari kebutuhan untuk inisialisasi, variabel memiliki nilai akhir langsung dari mati. Beberapa pengecualian adalah ketika inisialisasi tergantung pada beberapa kondisi:
Saya percaya bahwa menulis kasus seperti ini adalah ide yang bagus:
Saya. E. secara eksplisit menyatakan bahwa beberapa inisialisasi yang masuk akal dari variabel Anda dilakukan.
Variabel anggota
Di sini saya setuju dengan apa yang dikatakan oleh penjawab lain: Ini harus selalu diinisialisasi oleh konstruktor / daftar penginisialisasi. Kalau tidak, Anda sulit memastikan konsistensi di antara anggota Anda. Dan jika Anda memiliki seperangkat anggota yang tampaknya tidak membutuhkan inisialisasi dalam semua kasus, perbaiki kelas Anda, tambahkan anggota tersebut dalam kelas turunan di mana mereka selalu dibutuhkan.
Buffer
Di sinilah saya tidak setuju dengan jawaban yang lain. Ketika orang menjadi religius tentang menginisialisasi variabel, mereka sering berakhir menginisialisasi buffer seperti ini:
Saya percaya ini hampir selalu berbahaya: Satu-satunya efek dari inisialisasi ini adalah mereka membuat alat seperti
valgrind
tidak berdaya. Kode apa pun yang membaca lebih banyak dari buffer yang diinisialisasi daripada seharusnya sangat mungkin bug. Tetapi dengan inisialisasi, bug itu tidak dapat diekspos olehvalgrind
. Jadi jangan menggunakannya kecuali Anda benar-benar bergantung pada memori yang diisi dengan nol (dan dalam hal ini, berikan komentar yang mengatakan apa yang Anda butuhkan untuk nol).Saya juga sangat merekomendasikan menambahkan target ke sistem build Anda yang menjalankan seluruh testuite di bawah
valgrind
atau alat serupa untuk mengekspos bug sebelum-inisialisasi dan kebocoran memori. Ini lebih berharga daripada semua pra-inisialisasi variabel. Ituvalgrind
target yang harus dijalankan secara rutin, yang paling penting sebelum kode apapun go public.Variabel Global
Anda tidak dapat memiliki variabel global yang tidak diinisialisasi (setidaknya dalam C / C ++ dll), jadi pastikan inisialisasi ini adalah yang Anda inginkan.
sumber
Base& b = foo() ? new Derived1 : new Derived2;
Base &b = base_factory(which);
. Ini sangat berguna jika Anda perlu memanggil kode lebih dari sekali atau jika memungkinkan Anda membuat hasilnya konstan.?:
adalah PITA, dan fungsi pabrik masih berlebihan. Kasus-kasus ini sedikit dan jarang, tetapi mereka memang ada.Kompiler C, C ++ atau Objective-C yang layak dengan set opsi kompiler yang tepat akan memberi tahu Anda pada waktu kompilasi jika suatu variabel digunakan sebelum nilainya ditetapkan. Karena dalam bahasa-bahasa ini menggunakan nilai variabel yang tidak diinisialisasi adalah perilaku yang tidak terdefinisi, "tetapkan nilai sebelum Anda gunakan" bukanlah petunjuk, atau pedoman, atau praktik yang baik, itu adalah persyaratan 100%; jika tidak, program Anda benar-benar rusak. Dalam bahasa lain, seperti Java dan Swift, kompiler tidak akan pernah mengizinkan Anda untuk menggunakan variabel sebelum diinisialisasi.
Ada perbedaan logis antara "inisialisasi" dan "setel nilai". Jika saya ingin menemukan tingkat konversi antara dolar dan euro, dan tulis "double rate = 0,0;" maka variabel memiliki nilai yang ditetapkan, tetapi tidak diinisialisasi. 0,0 yang disimpan di sini tidak ada hubungannya dengan hasil yang benar. Dalam situasi ini, jika karena bug Anda tidak pernah menyimpan tingkat konversi yang benar, kompiler tidak memiliki kesempatan untuk memberi tahu Anda. Jika Anda baru saja menulis "tarif ganda;" dan tidak pernah menyimpan tingkat konversi yang berarti, kompiler akan memberi tahu Anda.
Jadi: Jangan menginisialisasi variabel hanya karena kompiler memberitahu Anda itu digunakan tanpa diinisialisasi. Itu menyembunyikan bug. Masalah sebenarnya adalah bahwa Anda menggunakan variabel yang seharusnya tidak Anda gunakan, atau bahwa pada satu jalur kode Anda tidak menetapkan nilai. Perbaiki masalahnya, jangan sembunyikan.
Jangan menginisialisasi variabel hanya karena kompiler mungkin memberi tahu Anda itu digunakan tanpa diinisialisasi. Sekali lagi, Anda menyembunyikan masalah.
Deklarasikan variabel yang dekat untuk digunakan. Ini meningkatkan peluang bahwa Anda dapat menginisialisasinya dengan nilai yang bermakna pada titik deklarasi.
Hindari menggunakan kembali variabel. Saat Anda menggunakan kembali variabel, kemungkinan besar diinisialisasi ke nilai yang tidak berguna saat Anda menggunakannya untuk tujuan kedua.
Telah dikomentari bahwa beberapa kompiler memiliki negatif palsu, dan memeriksa inisialisasi setara dengan masalah penghentian. Keduanya dalam praktiknya tidak relevan. Jika kompiler, seperti dikutip, tidak dapat menemukan penggunaan variabel tidak diinisialisasi sepuluh tahun setelah bug dilaporkan, maka sekarang saatnya untuk mencari kompilator alternatif. Java mengimplementasikan ini dua kali; sekali di kompiler, sekali di verifikasi, tanpa masalah. Cara mudah untuk mengatasi masalah penghentian bukan untuk mengharuskan variabel diinisialisasi sebelum digunakan, tetapi bahwa itu diinisialisasi sebelum digunakan dengan cara yang dapat diperiksa oleh algoritma sederhana dan cepat.
sumber