Saya punya pertanyaan yang sangat sederhana yang membuat saya bingung untuk waktu yang lama. Saya berurusan dengan jaringan dan basis data sehingga banyak data yang saya tangani adalah penghitung 32-bit dan 64-bit (tidak ditandatangani), id identifikasi 32-bit dan 64-bit (juga tidak memiliki pemetaan tanda yang berarti). Saya praktis tidak pernah berurusan dengan masalah kata nyata yang dapat dinyatakan sebagai angka negatif.
Saya dan rekan kerja saya secara rutin menggunakan tipe yang tidak ditandatangani seperti uint32_t
dan uint64_t
untuk masalah ini dan karena itu sering terjadi kami juga menggunakannya untuk indeks array dan penggunaan integer umum lainnya.
Pada saat yang sama berbagai panduan pengkodean yang saya baca (misalnya Google) mencegah penggunaan tipe integer yang tidak ditandatangani, dan sejauh yang saya tahu baik Java maupun Scala tidak memiliki tipe integer yang tidak ditandatangani.
Jadi, saya tidak tahu apa yang benar untuk dilakukan: menggunakan nilai yang ditandatangani di lingkungan kita akan sangat merepotkan, pada saat yang sama panduan pengkodean bersikeras untuk melakukan hal ini.
sumber
Jawaban:
Ada dua aliran pemikiran tentang ini, dan tidak ada yang akan setuju.
Yang pertama berpendapat bahwa ada beberapa konsep yang secara inheren tidak ditandai - seperti indeks array. Tidak masuk akal untuk menggunakan nomor yang ditandatangani untuk mereka karena dapat menyebabkan kesalahan. Itu juga dapat memberlakukan batasan yang tidak perlu pada hal-hal - array yang menggunakan indeks 32-bit yang ditandatangani hanya dapat mengakses 2 miliar entri, sementara beralih ke nomor 32-bit yang tidak ditandatangani memungkinkan 4 miliar entri.
Yang kedua berpendapat bahwa dalam program apa pun yang menggunakan angka yang tidak ditandatangani, cepat atau lambat Anda akan berakhir melakukan aritmatika bertanda-tangani campuran. Ini dapat memberikan hasil yang aneh dan tidak terduga: memberikan nilai besar yang tidak ditandatangani untuk ditandatangani memberi angka negatif, dan sebaliknya memberikan angka negatif ke yang tidak ditandatangani memberikan nilai positif yang besar. Ini bisa menjadi sumber kesalahan besar.
sumber
int
lebih pendek untuk mengetik :)int
lebih dari cukup untuk indeks array 99,99% dari waktu. Masalah aritmatika yang ditandatangani dan tidak ditandatangani jauh lebih umum, dan karenanya didahulukan dalam hal apa yang harus dihindari. Ya, kompiler memperingatkan Anda tentang hal ini, tetapi berapa banyak peringatan yang Anda dapatkan ketika menyusun proyek yang cukup besar? Mengabaikan peringatan itu berbahaya, dan praktik yang buruk, tetapi di dunia nyata ...size_t
, kecuali ada case-case khusus alasan bagus sebaliknya.Pertama-tama, pedoman pengkodean Google C ++ bukan yang sangat baik untuk diikuti: pedoman ini menghindari hal-hal seperti pengecualian, dorongan, dll. Yang merupakan bahan pokok dari C ++ modern. Kedua, hanya karena pedoman tertentu bekerja untuk perusahaan X tidak berarti itu akan cocok untuk Anda. Saya akan terus menggunakan tipe yang tidak ditandatangani, karena Anda membutuhkannya.
Aturan praktis yang layak untuk C ++ adalah: lebih suka
int
kecuali Anda memiliki alasan yang baik untuk menggunakan sesuatu yang lain.sumber
return false
jika invarian itu tidak ditetapkan. Jadi, Anda dapat memisahkan hal-hal dan menggunakan fungsi init untuk objek Anda, atau Anda dapat melemparstd::runtime_error
, membiarkan tumpukan terjadi, dan membiarkan semua objek RAII Anda membersihkan sendiri secara otomatis dan Anda pengembang dapat menangani pengecualian di tempat yang nyaman untuk Anda melakukannya.nullptr
? mengembalikan objek "default" (apa pun artinya)? Anda tidak memecahkan apa pun - Anda baru saja menyembunyikan masalah di bawah karpet, dan berharap tidak ada yang tahu.signal(6)
? Jika Anda menggunakan pengecualian, 50% pengembang yang tahu cara menangani mereka dapat menulis kode yang baik, dan sisanya dapat dilakukan oleh rekan-rekan mereka.Jawaban lainnya tidak memiliki contoh dunia nyata, jadi saya akan menambahkan satu. Salah satu alasan mengapa saya (secara pribadi) mencoba menghindari tipe yang tidak ditandatangani.
Pertimbangkan untuk menggunakan size_t standar sebagai indeks array:
Ok, sangat normal. Kemudian, pertimbangkan kami memutuskan untuk mengubah arah loop karena beberapa alasan:
Dan sekarang tidak berfungsi. Jika kita digunakan
int
sebagai iterator, tidak akan ada masalah. Saya telah melihat kesalahan seperti itu dua kali dalam dua tahun terakhir. Setelah itu terjadi dalam produksi dan sulit untuk di-debug.Alasan lain bagi saya adalah peringatan yang mengganggu, yang membuat Anda menulis sesuatu seperti ini setiap saat :
Ini adalah hal-hal kecil, tetapi mereka bertambah. Saya merasa kode ini lebih bersih jika hanya bilangan bulat bertanda yang digunakan di mana-mana.
Sunting: Tentu, contohnya terlihat bodoh, tapi saya melihat orang membuat kesalahan ini. Jika ada cara mudah untuk menghindarinya, mengapa tidak menggunakannya?
Ketika saya mengkompilasi bagian kode berikut dengan VS2015 atau GCC saya tidak melihat peringatan dengan pengaturan peringatan default (bahkan dengan -Dinding untuk GCC). Anda harus meminta -Wextra untuk mendapatkan peringatan tentang hal ini di GCC. Ini adalah salah satu alasan Anda harus selalu mengkompilasi dengan Wall dan Wextra (dan menggunakan analisa statis), tetapi dalam banyak proyek kehidupan nyata orang tidak melakukannya.
sumber
for (size_t i = n - 1; i < n; --i)
untuk membuatnya bekerja dengan benar.size_t
terbalik, ada pedoman pengkodean dalam gayafor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
int
? :)int
ini tidak cukup besar untuk menampung semua nilai validsize_t
. Khususnya,int
memungkinkan angka hanya hingga 2 ^ 15-1, dan biasanya melakukannya pada sistem yang memiliki batas alokasi memori 2 ^ 16 (atau dalam kasus tertentu bahkan lebih tinggi).long
mungkin taruhan yang lebih aman, meskipun masih belum dijamin berhasil. Hanyasize_t
dijamin untuk bekerja di semua platform dan dalam semua kasus.Masalahnya di sini adalah bahwa Anda menulis loop dengan cara yang tidak jelas yang mengarah ke perilaku yang salah. Konstruksi loop seperti pemula yang diajarkan untuk tipe yang ditandatangani (yang OK dan benar) tetapi tidak cocok untuk nilai yang tidak ditandatangani. Tapi ini tidak bisa berfungsi sebagai argumen balasan terhadap penggunaan tipe yang tidak ditandatangani, tugas di sini adalah untuk mendapatkan loop Anda dengan benar. Dan ini dapat dengan mudah diperbaiki agar dapat bekerja dengan baik untuk tipe yang tidak ditandatangani seperti:
Perubahan ini hanya mengembalikan urutan perbandingan dan operasi penurunan dan menurut pendapat saya cara yang paling efektif, tidak mengganggu, bersih dan singkat untuk menangani penghitung yang tidak ditandatangani dalam loop mundur. Anda akan melakukan hal yang sama (secara intuitif) saat menggunakan loop sementara:
Tidak ada underflow yang dapat terjadi, case dari wadah kosong tertutup secara implisit, seperti pada varian terkenal untuk loop counter yang ditandatangani, dan badan loop mungkin tetap tidak berubah dalam perbandingan ke counter yang ditandatangani atau loop ke depan. Anda hanya perlu membiasakan diri pada konstruksi loop pertama yang agak aneh. Tetapi setelah Anda melihat bahwa belasan kali tidak ada yang tidak dapat dipahami lagi.
Saya akan beruntung jika kursus pemula tidak hanya menunjukkan loop yang benar untuk masuk tetapi juga untuk tipe yang tidak ditandatangani. Ini akan menghindari beberapa kesalahan yang seharusnya disalahkan oleh IMHO kepada pengembang yang tidak sengaja alih-alih menyalahkan tipe yang tidak ditandatangani.
HTH
sumber
Bilangan bulat bertanda ada karena suatu alasan.
Pertimbangkan, misalnya, menyerahkan data sebagai byte individual, misalnya dalam paket jaringan atau buffer file. Anda kadang-kadang dapat menemukan binatang seperti integer 24-bit Mudah digeser dari tiga bilangan bulat 8-bit yang tidak ditandatangani, tidak begitu mudah dengan bilangan bulat bertanda 8-bit.
Atau pikirkan algoritma yang menggunakan tabel pencarian karakter. Jika karakter adalah bilangan bulat 8-bit unsigned, Anda bisa mengindeks tabel pencarian dengan nilai karakter. Namun, apa yang Anda lakukan jika bahasa pemrograman tidak mendukung bilangan bulat yang tidak ditandai? Anda akan memiliki indeks negatif ke array. Yah, saya kira Anda bisa menggunakan sesuatu seperti
charval + 128
tapi itu hanya jelek.Banyak format file, pada kenyataannya, menggunakan bilangan bulat yang tidak ditandatangani dan jika bahasa pemrograman aplikasi tidak mendukung bilangan bulat yang tidak ditandatangani, itu bisa menjadi masalah.
Kemudian pertimbangkan nomor urut TCP. Jika Anda menulis kode pemrosesan TCP, Anda pasti ingin menggunakan bilangan bulat yang tidak ditandatangani.
Terkadang, efisiensi sangat penting sehingga Anda benar-benar membutuhkan sedikit tambahan bilangan bulat yang tidak ditandatangani. Pertimbangkan misalnya perangkat IoT yang dikirim dalam jutaan. Banyak sumber daya pemrograman dapat dibenarkan untuk dibelanjakan pada optimasi mikro.
Saya berpendapat bahwa pembenaran untuk menghindari penggunaan tipe integer yang tidak ditandatangani (aritmatika tanda campuran, perbandingan tanda campuran) dapat diatasi oleh kompiler dengan peringatan yang tepat. Peringatan semacam itu biasanya tidak diaktifkan secara default, tetapi lihat misalnya
-Wextra
atau secara terpisah-Wsign-compare
(diaktifkan otomatis dalam bahasa C oleh-Wextra
, meskipun saya pikir itu tidak diaktifkan otomatis dalam bahasa C ++) dan-Wsign-conversion
.Meskipun demikian, jika ragu, gunakan jenis yang ditandatangani. Sering kali, itu adalah pilihan yang berfungsi dengan baik. Dan aktifkan peringatan kompiler itu!
sumber
Ada banyak kasus di mana bilangan bulat tidak benar-benar mewakili angka, tetapi misalnya topeng bit, id, dll. Pada dasarnya kasus di mana menambahkan 1 ke bilangan bulat tidak memiliki hasil yang berarti. Dalam kasus tersebut, gunakan yang tidak ditandatangani.
Ada banyak kasus di mana Anda melakukan aritmatika dengan bilangan bulat. Dalam kasus ini, gunakan bilangan bulat yang ditandatangani, untuk menghindari kesalahan perilaku sekitar nol. Lihat banyak contoh dengan loop, di mana menjalankan loop ke nol baik menggunakan kode yang sangat tidak intuitif atau rusak karena penggunaan nomor yang tidak ditandatangani. Ada argumen "tetapi indeks tidak pernah negatif" - tentu saja, tetapi perbedaan indeks misalnya negatif.
Dalam kasus yang sangat jarang di mana indeks melebihi 2 ^ 31 tetapi tidak 2 ^ 32, Anda tidak menggunakan bilangan bulat yang tidak ditandatangani, Anda menggunakan bilangan bulat 64 bit.
Akhirnya, jebakan yang bagus: Dalam satu lingkaran "untuk (i = 0; i <n; ++ i) a [i] ..." jika saya unsigned 32 bit, dan memori melebihi 32 bit alamat, kompilator tidak dapat mengoptimalkan akses ke [i] dengan menambah pointer, karena pada i = 2 ^ 32 - 1 saya membungkus. Bahkan ketika n tidak pernah menjadi sebesar itu. Menggunakan bilangan bulat yang ditandatangani menghindari hal ini.
sumber
Akhirnya, saya menemukan jawaban yang sangat bagus di sini: "Secure Programming Cookbook" oleh J.Viega dan M.Messier ( http://shop.oreilly.com/product/9780596003944.do )
Masalah keamanan dengan bilangan bulat yang ditandatangani:
Ada masalah dengan konversi yang tidak ditandatangani yang ditandatangani <-> sehingga tidak disarankan menggunakan campuran.
sumber