Saya menemukan poin yang menarik hari ini dalam ulasan tentang Tinjauan Kode . @Veedrac direkomendasikan dalam jawaban ini bahwa tipe ukuran variabel (misalnya int
dan long
) diganti dengan tipe ukuran tetap seperti uint64_t
dan 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 int
dan 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 int
di era C99 dan seterusnya?
sumber
Jawaban:
Ada mitos umum dan berbahaya bahwa jenis seperti
uint32_t
menyelamatkan programmer dari harus khawatir tentang ukuranint
. Meskipun akan sangat membantu jika Komite Standar menentukan cara mendeklarasikan bilangan bulat dengan semantik independen-mesin, tipe yang tidak ditandatangani sepertiuint32_t
memiliki semantik yang terlalu longgar untuk memungkinkan kode ditulis dengan cara yang bersih dan portabel; lebih lanjut, tipe yang ditandatangani sepertiint32
semantik yang untuk banyak cara aplikasi didefinisikan dengan tidak perlu dengan ketat dan dengan demikian menghalangi apa yang seharusnya menjadi optimasi yang bermanfaat.Pertimbangkan, misalnya:
Pada mesin yang
int
tidak dapat menyimpan 4294967295, atau dapat menampung 18446744065119617025, fungsi pertama akan ditentukan untuk semua nilain
danexponent
, dan perilakunya tidak akan terpengaruh oleh ukuranint
; lebih lanjut, standar tidak akan mengharuskan itu menghasilkan perilaku yang berbeda pada mesin dengan ukuran berapa pun dariint
beberapa nilain
danexponent
, bagaimanapun, akan menyebabkannya untuk memanggil perilaku tidak terdefinisi pada mesin di mana 4294967295 diwakili sebagaiint
tetapi 18446744065119617025 tidak.Fungsi kedua akan menghasilkan Perilaku Tidak Terdefinisi untuk beberapa nilai
n
danexponent
pada mesin di manaint
tidak dapat menyimpan 4611686014132420609, tetapi akan menghasilkan perilaku yang ditentukan untuk semua nilain
danexponent
pada semua mesin di mana ia dapat (spesifikasi untukint32_t
menyiratkan bahwa perilaku pembungkus dua komplemen pada mesin di mana ia lebih kecil dariint
).Secara historis, meskipun Standar tidak mengatakan apa-apa tentang apa yang harus dilakukan penyusun dengan
int
overflowupow
, penyusun akan secara konsisten menghasilkan perilaku yang sama seolah-olahint
sudah cukup besar untuk tidak meluap. Sayangnya, beberapa kompiler yang lebih baru mungkin berusaha untuk "mengoptimalkan" program dengan menghilangkan perilaku yang tidak diamanatkan oleh Standar.sumber
pow
, ingat kode ini hanyalah contoh dan tidak memenuhiexponent=0
!exponent=1
akan 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.N^(2^exponent)
dihitung adalah , tetapi perhitungan bentukN^(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.uint32_t
31 oleh tidak akan pernah menghasilkan UB, tetapi yang efisien cara untuk menghitung 31 ^ N mencakup perhitungan 31 ^ (2 ^ N), yang akanint32_t
kadang-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.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 typedefsize_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
, danlong long
masih berguna.IME, masih umum untuk program C dan C ++ digunakan
int
untuk 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 menggunakanlong
, yang dijamin setidaknya 32 bit.Masalahnya adalah bahwa standar C tidak menentukan ukuran maksimum untuk tipe integer. Ada implementasi yang
long
64 bit, yang menggandakan penggunaan memori Anda. Dan jika inilong
adalah elemen dari array dengan jutaan item, Anda akan menghancurkan memori seperti orang gila.Jadi, tidak ada
int
jugalong
jenis yang cocok untuk digunakan di sini jika Anda ingin program Anda menjadi lintas platform dan hemat memori. Masukkanint_least32_t
.long
, menghindari masalah pemotonganint
int
, menghindari memori yang terbuang dari 64-bitlong
.int
OTOH, misalkan Anda tidak perlu jumlah besar atau array besar tetapi Anda membutuhkan kecepatan. Dan
int
mungkin cukup besar di semua platform, tetapi itu belum tentu tipe tercepat: Sistem 64-bit biasanya masih memiliki 32-bitint
. Tapi Anda bisa menggunakanint_fast16_t
dan mendapatkan “tercepat” jenis, apakah ituint
,long
ataulong long
.Jadi, ada kasus penggunaan praktis untuk jenis dari
<stdint.h>
. Tipe integer standar tidak berarti apa - apa. Terutamalong
, yang mungkin 32 atau 64 bit, dan mungkin atau mungkin tidak cukup besar untuk menampung sebuah pointer, tergantung pada kemauan penulis kompiler.sumber
uint_least32_t
adalah bahwa interaksinya dengan tipe lain bahkan lebih lemah dari yang ditentukanuint32_t
. IMHO, Standar harus mendefinisikan tipe sepertiuwrap32_t
danunum32_t
, dengan semantik bahwa setiap kompiler yang mendefinisikan tipeuwrap32_t
, harus mempromosikan sebagai tipe yang tidak ditandatangani dalam kasus yang sama seperti yang akan dipromosikan jikaint
32 bit, dan setiap kompiler yang mendefinisikan tipeunum32_t
harus memastikan bahwa promosi aritmatika dasar selalu mengonversinya menjadi tipe bertanda tangan yang mampu mempertahankan nilainya.intN_t
danuintN_t
, dan perilaku yang didefinisikan akan konsisten denganintN_t
danuintN_t
, tetapi yang akan memberikan kompiler kebebasan dalam hal kode diberi nilai di luar jangkauan mereka [memungkinkan semantik mirip dengan yang mungkin dimaksudkan untukuint_least32_t
, tetapi tanpa ketidakpastian seperti apakah menambahkanuint_least16_t
danint32_t
akan menghasilkan hasil yang ditandatangani atau usnigned.