Apakah tipe lebar variabel telah diganti dengan tipe tetap di C modern?

21

Saya menemukan poin yang menarik hari ini dalam ulasan tentang Tinjauan Kode . @Veedrac direkomendasikan dalam jawaban ini bahwa tipe ukuran variabel (misalnya intdan long) diganti dengan tipe ukuran tetap seperti uint64_tdan uint32_t. Kutipan dari komentar jawaban itu:

Ukuran int dan panjang (dan dengan demikian nilai-nilai yang dapat mereka pegang) bergantung pada platform. Di sisi lain, int32_t selalu sepanjang 32 bit. Menggunakan int berarti kode Anda bekerja secara berbeda pada platform yang berbeda, yang umumnya bukan yang Anda inginkan.

Alasan di balik standar tidak memperbaiki tipe umum sebagian dijelaskan di sini oleh @supercat. C ditulis untuk portabel di seluruh arsitektur, bertentangan dengan perakitan yang biasanya digunakan untuk pemrograman sistem pada saat itu.

Saya pikir niat desain awalnya bahwa setiap jenis selain int menjadi hal terkecil yang dapat menangani jumlah berbagai ukuran, dan int itu menjadi ukuran "tujuan umum" paling praktis yang dapat menangani +/- 32767.

Bagi saya, saya selalu menggunakan intdan tidak terlalu khawatir tentang alternatif. Saya selalu berpikir bahwa ini adalah tipe paling dengan kinerja terbaik, akhir cerita. Satu-satunya tempat yang saya pikir lebar tetap akan berguna adalah ketika menyandikan data untuk penyimpanan atau untuk transfer melalui jaringan. Saya jarang melihat jenis lebar tetap dalam kode yang ditulis oleh orang lain juga.

Apakah saya terjebak di tahun 70-an atau apakah sebenarnya ada alasan untuk menggunakan intdi era C99 dan seterusnya?

jacwah
sumber
1
Sebagian orang meniru orang lain. Saya percaya sebagian besar kode fixed-bit-type dibuat nurani. Tidak ada alasan untuk mengatur ukuran juga tidak. Saya memiliki kode yang dibuat terutama pada platform 16 bit (MS-DOS dan Xenix dari 80-an), yang baru saja dikompilasi dan dijalankan hari ini pada 64 dan manfaat dari ukuran dan pengalamatan kata baru, hanya mengompilasinya. Artinya serialisasi untuk mengekspor / mengimpor data adalah desain arsitektur yang sangat penting agar tetap portabel.
Luciano

Jawaban:

7

Ada mitos umum dan berbahaya bahwa jenis seperti uint32_tmenyelamatkan programmer dari harus khawatir tentang ukuran int. Meskipun akan sangat membantu jika Komite Standar menentukan cara mendeklarasikan bilangan bulat dengan semantik independen-mesin, tipe yang tidak ditandatangani seperti uint32_tmemiliki semantik yang terlalu longgar untuk memungkinkan kode ditulis dengan cara yang bersih dan portabel; lebih lanjut, tipe yang ditandatangani seperti int32semantik yang untuk banyak cara aplikasi didefinisikan dengan tidak perlu dengan ketat dan dengan demikian menghalangi apa yang seharusnya menjadi optimasi yang bermanfaat.

Pertimbangkan, misalnya:

uint32_t upow(uint32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int32_t spow(int32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

Pada mesin yang inttidak dapat menyimpan 4294967295, atau dapat menampung 18446744065119617025, fungsi pertama akan ditentukan untuk semua nilai ndan exponent, dan perilakunya tidak akan terpengaruh oleh ukuran int; lebih lanjut, standar tidak akan mengharuskan itu menghasilkan perilaku yang berbeda pada mesin dengan ukuran berapa pun dari int beberapa nilai ndan exponent, bagaimanapun, akan menyebabkannya untuk memanggil perilaku tidak terdefinisi pada mesin di mana 4294967295 diwakili sebagai inttetapi 18446744065119617025 tidak.

Fungsi kedua akan menghasilkan Perilaku Tidak Terdefinisi untuk beberapa nilai ndan exponentpada mesin di mana inttidak dapat menyimpan 4611686014132420609, tetapi akan menghasilkan perilaku yang ditentukan untuk semua nilai ndan exponentpada semua mesin di mana ia dapat (spesifikasi untuk int32_tmenyiratkan bahwa perilaku pembungkus dua komplemen pada mesin di mana ia lebih kecil dari int).

Secara historis, meskipun Standar tidak mengatakan apa-apa tentang apa yang harus dilakukan penyusun dengan intoverflow upow, penyusun akan secara konsisten menghasilkan perilaku yang sama seolah-olah intsudah cukup besar untuk tidak meluap. Sayangnya, beberapa kompiler yang lebih baru mungkin berusaha untuk "mengoptimalkan" program dengan menghilangkan perilaku yang tidak diamanatkan oleh Standar.

supercat
sumber
3
Siapa pun yang ingin menerapkan secara manual pow, ingat kode ini hanyalah contoh dan tidak memenuhi exponent=0!
Mark Hurd
1
Saya pikir Anda harus menggunakan operator penurunan awalan bukan postfix, saat ini sedang melakukan 1 multiplikasi tambahan misalnya exponent=1akan menghasilkan n dikalikan dengan sendirinya sekali, karena penurunan dilakukan setelah pemeriksaan, jika kenaikan dilakukan sebelum pemeriksaan ( yaitu --exponent), tidak ada perkalian yang akan dilakukan dan n itu sendiri akan dikembalikan.
ALXGTV
2
@MarkHurd: Fungsi ini tidak disebutkan dengan baik, karena apa yang sebenarnya N^(2^exponent)dihitung adalah , tetapi perhitungan bentuk N^(2^exponent)sering digunakan dalam perhitungan fungsi eksponensial, dan mod-4294967296 eksponensial berguna untuk hal-hal seperti menghitung hash dari rangkaian dua string yang hash dikenal.
supercat
1
@ALXGTV: Fungsi ini dimaksudkan untuk menggambarkan sesuatu yang menghitung sesuatu yang berhubungan dengan kekuasaan. Apa yang sebenarnya dihitung adalah N ^ (2 ^ eksponen), yang merupakan bagian dari komputasi eksponen N ^ secara efisien, dan mungkin gagal bahkan jika N kecil (perkalian berulang dari uint32_t31 oleh tidak akan pernah menghasilkan UB, tetapi yang efisien cara untuk menghitung 31 ^ N mencakup perhitungan 31 ^ (2 ^ N), yang akan
supercat
Saya pikir ini bukan argumen yang bagus. Tujuannya bukan untuk membuat fungsi didefinisikan untuk semua input, masuk akal atau tidak; itu untuk dapat alasan tentang ukuran dan melimpah. int32_tkadang-kadang memiliki overflow yang didefinisikan dan kadang-kadang tidak, yang sepertinya Anda sebutkan, tampaknya tidak terlalu penting dibandingkan dengan fakta bahwa hal itu membuat saya beralasan untuk mencegah overflow. Dan jika Anda ingin didefinisikan overflow, kemungkinan Anda menginginkan hasil modulo beberapa nilai tetap - jadi Anda tetap menggunakan jenis lebar tetap.
Veedrac
4

Untuk nilai yang terkait erat dengan pointer (dan dengan demikian, dengan jumlah memori yang dapat dialamatkan) seperti ukuran buffer, indeks array, dan Windows ' lParam, masuk akal untuk memiliki tipe integer dengan ukuran yang bergantung pada arsitektur. Jadi, tipe berukuran variabel masih berguna. Ini adalah mengapa kita memiliki typedef size_t, ptrdiff_t, intptr_t, dll Mereka memiliki menjadi typedef karena tidak ada built-in C bilangan bulat jenis perlu pointer berukuran.

Jadi pertanyaannya adalah benar-benar apakah char, short, int, long, dan long longmasih berguna.

IME, masih umum untuk program C dan C ++ digunakan intuntuk sebagian besar hal. Dan sebagian besar waktu (yaitu, ketika nomor Anda berada di kisaran ± 32.767 dan Anda tidak memiliki persyaratan kinerja yang ketat), ini berfungsi dengan baik.

Tetapi bagaimana jika Anda perlu bekerja dengan angka dalam kisaran 17-32 bit (seperti populasi kota besar)? Anda bisa menggunakan int, tetapi itu akan menjadi penyandian keras ketergantungan platform. Jika Anda ingin secara ketat mematuhi standar, Anda dapat menggunakan long, yang dijamin setidaknya 32 bit.

Masalahnya adalah bahwa standar C tidak menentukan ukuran maksimum untuk tipe integer. Ada implementasi yang long64 bit, yang menggandakan penggunaan memori Anda. Dan jika ini longadalah elemen dari array dengan jutaan item, Anda akan menghancurkan memori seperti orang gila.

Jadi, tidak ada intjuga longjenis yang cocok untuk digunakan di sini jika Anda ingin program Anda menjadi lintas platform dan hemat memori. Masukkan int_least32_t.

  • Kompiler I16L32 Anda memberi Anda 32-bit long, menghindari masalah pemotonganint
  • Kompiler I32L64 Anda memberi Anda 32-bit int, menghindari memori yang terbuang dari 64-bit long.
  • Kompiler I36L72 Anda memberi Anda 36-bit int

OTOH, misalkan Anda tidak perlu jumlah besar atau array besar tetapi Anda membutuhkan kecepatan. Dan intmungkin cukup besar di semua platform, tetapi itu belum tentu tipe tercepat: Sistem 64-bit biasanya masih memiliki 32-bit int. Tapi Anda bisa menggunakan int_fast16_tdan mendapatkan “tercepat” jenis, apakah itu int, longatau long long.

Jadi, ada kasus penggunaan praktis untuk jenis dari <stdint.h>. Tipe integer standar tidak berarti apa - apa. Terutama long, yang mungkin 32 atau 64 bit, dan mungkin atau mungkin tidak cukup besar untuk menampung sebuah pointer, tergantung pada kemauan penulis kompiler.

dan04
sumber
Masalah dengan tipe suka uint_least32_tadalah bahwa interaksinya dengan tipe lain bahkan lebih lemah dari yang ditentukan uint32_t. IMHO, Standar harus mendefinisikan tipe seperti uwrap32_tdan unum32_t, dengan semantik bahwa setiap kompiler yang mendefinisikan tipe uwrap32_t, harus mempromosikan sebagai tipe yang tidak ditandatangani dalam kasus yang sama seperti yang akan dipromosikan jika int32 bit, dan setiap kompiler yang mendefinisikan tipe unum32_tharus memastikan bahwa promosi aritmatika dasar selalu mengonversinya menjadi tipe bertanda tangan yang mampu mempertahankan nilainya.
supercat
Selain itu, Standar ini juga dapat mendefinisikan jenis penyimpanan dan aliasing yang kompatibel dengan intN_tdan uintN_t, dan perilaku yang didefinisikan akan konsisten dengan intN_tdan uintN_t, tetapi yang akan memberikan kompiler kebebasan dalam hal kode diberi nilai di luar jangkauan mereka [memungkinkan semantik mirip dengan yang mungkin dimaksudkan untuk uint_least32_t, tetapi tanpa ketidakpastian seperti apakah menambahkan uint_least16_tdan int32_takan menghasilkan hasil yang ditandatangani atau usnigned.
supercat