size_t atau int untuk dimensi, indeks, dll

15

Dalam C ++, size_t(atau, lebih tepatnya T::size_typeyang "biasanya" size_t; yaitu, unsignedtipe) digunakan sebagai nilai balik untuk size(), argumen ke operator[], dll. (Lihat std::vector, et. Al.)

Di sisi lain, bahasa .NET menggunakan int(dan, opsional, long) untuk tujuan yang sama; bahkan, bahasa yang sesuai dengan CLS tidak diperlukan untuk mendukung jenis yang tidak ditandatangani .

Mengingat bahwa .NET lebih baru daripada C ++, sesuatu memberi tahu saya bahwa mungkin ada masalah menggunakan unsigned intbahkan untuk hal-hal yang "tidak mungkin" menjadi negatif seperti indeks atau panjang array. Apakah pendekatan C ++ "artefak historis" untuk kompatibilitas mundur? Atau adakah pertukaran desain nyata dan signifikan antara kedua pendekatan?

Mengapa ini penting? Nah ... apa yang harus saya gunakan untuk kelas multi-dimensi baru di C ++; size_tatau int?

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};
.Аn
sumber
6
Perlu dicatat: di beberapa tempat di .NET Framework, -1dikembalikan dari fungsi yang mengembalikan indeks, untuk menunjukkan "tidak ditemukan" atau "di luar jangkauan." Itu juga dikembalikan dari Compare()fungsi (implementasi IComparable). Int 32 bit dianggap sebagai tipe ketikan untuk nomor umum, karena saya harap alasan yang jelas.
Robert Harvey

Jawaban:

9

Mengingat bahwa .NET lebih baru daripada C ++, sesuatu memberi tahu saya bahwa mungkin ada masalah menggunakan unsigned int bahkan untuk hal-hal yang "tidak mungkin" menjadi negatif seperti indeks atau panjang array.

Iya. Untuk jenis aplikasi tertentu seperti pemrosesan gambar atau pemrosesan array, seringkali diperlukan untuk mengakses elemen relatif ke posisi saat ini:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

Dalam jenis aplikasi ini, Anda tidak dapat melakukan pemeriksaan rentang dengan bilangan bulat tanpa tanda tanpa berpikir dengan hati-hati:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

Alih-alih, Anda harus mengatur ulang ekspresi pemeriksaan rentang. Itulah perbedaan utama. Pemrogram juga harus mengingat aturan konversi bilangan bulat. Jika ragu, baca kembali http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions

Banyak aplikasi tidak perlu menggunakan indeks array yang sangat besar, tetapi mereka perlu melakukan pemeriksaan jangkauan. Selanjutnya, banyak programmer yang tidak terlatih untuk melakukan senam penataan ulang ekspresi ini. Satu peluang yang terlewatkan membuka pintu bagi suatu eksploitasi.

C # memang dirancang untuk aplikasi-aplikasi yang tidak akan membutuhkan lebih dari 2 ^ 31 elemen per array. Misalnya, aplikasi spreadsheet tidak perlu berurusan dengan banyak baris, kolom, atau sel. C # berurusan dengan batas atas dengan memeriksa aritmatika opsional yang dapat diaktifkan untuk blok kode dengan kata kunci tanpa mengacaukan opsi kompiler. Untuk alasan ini, C # mendukung penggunaan bilangan bulat yang ditandatangani. Ketika keputusan ini dianggap sama sekali, itu masuk akal.

C ++ sangat berbeda, dan lebih sulit untuk mendapatkan kode yang benar.

Mengenai pentingnya praktis memungkinkan aritmatika yang ditandatangani untuk menghapus potensi pelanggaran "prinsip takjub", satu kasus yang dimaksud adalah OpenCV, yang menggunakan integer 32-bit yang ditandatangani untuk indeks elemen matriks, ukuran array, jumlah saluran piksel, dll. Gambar pemrosesan adalah contoh dari domain pemrograman yang banyak menggunakan indeks array relatif. Underflow integer yang tidak ditandatangani (hasil negatif yang dibungkus) akan sangat menyulitkan implementasi algoritma.

rwong
sumber
Ini persis situasi saya; terima kasih atas contoh spesifiknya. (Ya, saya tahu ini, tetapi bisa bermanfaat untuk mengutip "otoritas yang lebih tinggi" untuk mengutip.)
Pada
1
@Dan: jika Anda perlu mengutip sesuatu, posting ini akan lebih baik.
rwong
1
@Dan: John Regehr secara aktif meneliti masalah ini dalam bahasa pemrograman. Lihat blog.regehr.org/archives/1401
rwong
Ada pendapat pelawan: gustedt.wordpress.com/2013/07/15/…
rwong
14

Jawaban ini benar-benar tergantung pada siapa yang akan menggunakan kode Anda, dan standar apa yang ingin mereka lihat.

size_t adalah ukuran integer dengan tujuan:

Tipe size_tini adalah tipe integer unsigned yang ditentukan-implementasi yang cukup besar untuk memuat ukuran dalam byte dari objek apa pun. (Spesifikasi C ++ 11 18.2.6)

Jadi, setiap kali Anda ingin bekerja dengan ukuran objek dalam byte, Anda harus menggunakan size_t. Sekarang dalam banyak kasus, Anda tidak menggunakan dimensi / indeks ini untuk menghitung byte, tetapi kebanyakan pengembang memilih untuk menggunakannya di size_tsana untuk konsistensi.

Perhatikan bahwa Anda harus selalu menggunakan size_tjika kelas Anda dimaksudkan untuk memiliki tampilan dan nuansa kelas STL. Semua kelas STL dalam penggunaan spesifikasi size_t. Adalah valid untuk compiler untuk mengetikkan size_tmenjadi unsigned int, dan itu juga berlaku untuk itu diketikkan ke unsigned long. Jika Anda menggunakan intatau longlangsung, pada akhirnya Anda akan menemui kompiler di mana seseorang yang berpikir kelas Anda mengikuti gaya STL terjebak karena Anda tidak mengikuti standar.

Adapun untuk menggunakan tipe yang ditandatangani, ada beberapa keuntungan:

  • Nama yang lebih pendek - sangat mudah bagi orang untuk mengetik int, tetapi jauh lebih sulit untuk mengacaukan kodenya unsigned int.
  • Satu integer untuk setiap ukuran - Hanya ada satu integer yang sesuai dengan CLS dari 32-bit, yaitu Int32. Di C ++, ada dua ( int32_tdan uint32_t). Ini dapat membuat interoperabilitas API lebih sederhana

Kerugian besar dari tipe yang ditandatangani adalah yang jelas: Anda kehilangan setengah dari domain Anda. Nomor yang ditandatangani tidak dapat dihitung setinggi nomor yang tidak ditandatangani. Ketika C / C ++ muncul, ini sangat penting. Salah satu yang diperlukan untuk dapat mengatasi kemampuan penuh prosesor, dan untuk itu Anda perlu menggunakan nomor yang tidak ditandatangani.

Untuk jenis-jenis aplikasi .NET yang ditargetkan, tidak ada kebutuhan yang kuat untuk indeks tak bertanda domain lengkap. Banyak tujuan untuk angka-angka seperti itu hanya tidak valid dalam bahasa yang dikelola (kumpulan memori muncul di pikiran). Juga, ketika. NET keluar, komputer 64-bit jelas masa depan. Kami masih jauh dari membutuhkan seluruh bilangan bulat 64-bit, jadi mengorbankan satu bit tidak sesakit sebelumnya. Jika Anda benar-benar membutuhkan 4 miliar indeks, Anda cukup beralih menggunakan integer 64-bit. Paling buruk, Anda menjalankannya pada mesin 32 bit dan agak lambat.

Saya melihat perdagangan sebagai salah satu kenyamanan. Jika Anda memiliki kekuatan komputasi yang cukup sehingga Anda tidak keberatan menyia-nyiakan sedikit tipe indeks yang tidak akan pernah Anda gunakan, maka cukup nyaman untuk mengetik intatau longberjalan menjauh darinya. Jika Anda benar-benar menginginkan yang terakhir, maka Anda mungkin harus memerhatikan ketandatanganan nomor Anda.

Cort Ammon - Reinstate Monica
sumber
katakanlah implementasi size()adalah return bar_ * baz_;; bukankah itu sekarang membuat masalah potensial dengan integer overflow (membungkus) yang saya tidak akan miliki jika saya tidak menggunakan size_t?
13аn
5
@Dan Anda dapat membuat case seperti itu di mana ints yang tidak ditandatangani akan menjadi masalah, dan dalam kasus itu yang terbaik untuk menggunakan fitur bahasa lengkap untuk menyelesaikannya. Namun, saya harus mengatakan bahwa itu akan menjadi konstruksi yang menarik untuk memiliki kelas di mana bar_ * baz_dapat meluap integer yang ditandatangani tetapi bukan integer yang tidak ditandatangani. Membatasi diri kita sendiri ke C ++, perlu dicatat bahwa overflow unsigned didefinisikan dalam spesifikasi, tetapi overflow yang ditandatangani adalah perilaku yang tidak terdefinisi, jadi jika modulo aritmatika bilangan bulat yang tidak diinginkan diinginkan, pasti gunakan, karena sebenarnya didefinisikan!
Cort Ammon - Reinstate Monica
1
@ Dan - jika yang size()meluap menandatangani perkalian, Anda berada di tanah bahasa UB. (dan dalam fwrapvmode, lihat berikutnya :) Ketika kemudian , dengan sedikit lebih sedikit, itu meluap- lipat dari multiplikasi yang tidak ditandatangani , Anda berada di tanah kode-pengguna-bug - Anda akan mengembalikan ukuran palsu. Jadi saya tidak berpikir unsigned membeli banyak di sini.
Martin Ba
4

Saya pikir jawaban rwong di atas sudah sangat baik menyoroti masalah.

Saya akan menambahkan 002 saya:

  • size_t, yaitu ukuran yang ...

    dapat menyimpan ukuran maksimum objek yang dimungkinkan secara teoritis dari semua jenis (termasuk larik).

    ... hanya diperlukan untuk indeks rentang saat sizeof(type)==1, yaitu, jika Anda berurusan dengan chartipe byte ( ). (Tapi, kami perhatikan, ini bisa lebih kecil dari tipe ptr :

  • Dengan demikian, xxx::size_typedapat digunakan dalam 99,9% kasus bahkan jika itu adalah tipe ukuran yang ditandatangani. (bandingkan ssize_t)
  • Fakta bahwa std::vectordan teman-teman memilih size_t, tipe yang tidak ditandatangani , untuk ukuran dan pengindeksan dianggap oleh beberapa orang sebagai cacat desain. Saya setuju. (Serius, ambil 5 menit dan saksikan pembicaraan kilat CppCon 2016: Jon Kalb “unsigned: A Guideline for Better Code" .)
  • Saat Anda merancang C ++ API hari ini, Anda berada di tempat yang sempit: Gunakan size_tagar konsisten dengan Perpustakaan Standar, atau gunakan ( bertanda tangan ) intptr_tatau ssize_tuntuk penghitungan pengindeksan yang mudah bug dan lebih mudah.
  • Jangan gunakan int32 atau int64 - gunakan intptr_tjika Anda ingin masuk, dan ingin ukuran kata mesin, atau gunakan ssize_t.

Untuk langsung menjawab pertanyaan, itu tidak sepenuhnya merupakan "artefak sejarah", karena masalah teoretis tentang perlunya mengatasi lebih dari separuh ("pengindeksan", atau) ruang alamat harus , aehm, ditangani entah bagaimana dalam bahasa tingkat rendah seperti C ++.

Kalau dipikir-pikir, saya, secara pribadi , berpikir, ini adalah cacat desain yang digunakan oleh Perpustakaan Standar tanpa tanda di size_tsemua tempat bahkan di mana itu tidak mewakili ukuran memori mentah, tetapi kapasitas data yang diketik, seperti untuk koleksi:

  • diberikan aturan promosi integer C ++ - -
  • tipe yang tidak ditandatangani hanya tidak membuat kandidat yang baik untuk tipe "semantik" untuk sesuatu seperti ukuran yang secara semantik tidak ditandatangani.

Saya akan mengulangi saran Jon di sini:

  • Pilih jenis untuk operasi yang didukung (bukan rentang nilai). (* 1)
  • Jangan gunakan jenis yang tidak ditandatangani di API Anda. Ini menyembunyikan bug tanpa keuntungan terbalik.
  • Jangan gunakan "unsigned" untuk jumlah. (* 2)

(* 1) yaitu unsigned == bitmask, jangan pernah menghitungnya (di sini ada pengecualian pertama - Anda mungkin perlu penghitung yang membungkus - ini harus jenis yang tidak ditandatangani.)

(* 2) jumlah berarti sesuatu yang Anda hitung dan / atau hitung.

Martin Ba
sumber
Apa yang Anda maksud dengan "memori datar penuh avilable"? Juga, tentu saja Anda tidak ingin ssize_t, yang didefinisikan sebagai liontin yang ditandatangani, size_tbukan intptr_t, yang dapat menyimpan penunjuk (bukan anggota) dan karenanya mungkin lebih besar?
Deduplicator
@Dupuplikator - Yah saya kira saya mungkin mendapatkan size_tdefinisi yang sedikit kacau. Lihat size_t vs. intptr dan en.cppreference.com/w/cpp/types/size_t Mempelajari sesuatu yang baru hari ini. :-) Saya pikir sisa argumen berdiri, saya akan melihat apakah saya dapat memperbaiki jenis yang digunakan.
Martin Ba
0

Saya hanya akan menambahkan bahwa untuk alasan kinerja saya biasanya menggunakan size_t, untuk memastikan bahwa kesalahan perhitungan menyebabkan underflow yang berarti pemeriksaan rentang (di bawah nol dan di atas ukuran ()) dapat dikurangi menjadi satu:

menggunakan int yang ditandatangani:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

menggunakan int unsigned:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}
asger
sumber
1
Anda benar - benar ingin menjelaskan yang lebih menyeluruh.
Martin Ba
Untuk membuat jawaban lebih bermanfaat, mungkin Anda bisa menggambarkan bagaimana bilangan bulat bilangan bulat atau perbandingan offset (ditandatangani dan tidak ditandatangani) terlihat dalam kode mesin dari berbagai vendor kompiler. Ada banyak kompiler C ++ online dan situs pembongkaran yang dapat menunjukkan kode mesin kompilasi yang sesuai untuk kode C ++ dan flag kompiler yang diberikan.
rwong
Saya mencoba menjelaskan ini lagi.
asger