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:
// move the pointers properly to copy data into a ring buffer.
char* putIntoRingBuffer(char* begin, char* end, char* get, char*put, char* newData, unsigned int dataLength)
{
// If dataLength is very large, we might overflow the pointer
// arithmetic, and end up with some very small pointer number,
// causing us to fail to realize we were trying to write past the
// end. Check this before we continue
if (put + dataLength < put)
{
RaiseError("Buffer overflow risk detected");
return 0;
}
...
// typical ring-buffer pointer manipulation followed...
}
Saya telah menambahkan lebih banyak komentar di rendisi saya, tetapi idenya sama. Jika put + dataLength
membungkus, itu akan lebih kecil dari put
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 karena ptr+dataLength
tidak akan pernah bisa di bawah ptr
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.
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.