C terus-menerus mengonversi nilai antartipe. Sebagian konversi itu kamu tulis sendiri dengan cast; sebagian besar dilakukan compiler secara diam-diam berdasarkan aturan yang tidak kamu pilih. Mengetahui mana yang mana adalah perbedaan antara "kenapa rata-rataku selalu 3?" dan kode yang benar-benar melakukan apa yang tertulis.
Konversi Implisit
Setiap kali nilai dari satu tipe bertemu konteks yang mengharapkan tipe lain, C mengonversinya:
Konversi yang tidak mungkin menghilangkan informasi (int ke double, char ke int, short ke long) adalah konversi pelebaran (widening) dan selalu aman. Konversi ke arah sebaliknya adalah penyempitan (narrowing) dan bisa menghilangkan data: 3.9 di atas menjadi 3 tanpa peringatan apa pun, kecuali kamu memintanya dengan -Wconversion.
Konversi Aritmetika Biasa
Ketika sebuah operator biner punya operand dengan tipe berbeda, C mengonversinya ke tipe bersama sebelum mengerjakan operasinya. Urutannya, dari bawah ke atas:
- Apa pun yang lebih kecil dari
int(char,short,_Bool) dipromosikan menjadiint. Ini disebut integer promotion dan selalu terjadi lebih dulu. - Jika salah satu sisi bertipe
long double, sisi lainnya menjadilong double. - Jika tidak, dan salah satunya
double, yang lain menjadidouble. - Jika tidak, dan salah satunya
float, yang lain menjadifloat. - Jika tidak, di antara tipe integer, yang peringkatnya lebih tinggi yang menang, dan jika peringkatnya sama, unsigned yang menang.
Aturan terakhir itulah yang menyebabkan bug sungguhan. Sisanya intuitif.
Integer promotion adalah alasan aritmetika char tidak overflow seperti yang mungkin kamu duga, sekaligus alasan menyimpan hasilnya kembali ke char justru overflow.
Cast Eksplisit
Cast adalah tipe tujuan di dalam tanda kurung:
(double)x
(int)3.9
(char)65
(unsigned int)n
Cast berlaku untuk ekspresi yang tepat berada sesudahnya, dan ikatannya sangat kuat, lebih kuat daripada *, /, atau +.
Baris pertama membagi sebagai integer (hasilnya 3) lalu mengonversi 3 menjadi 3.0, dan itu sudah terlambat. Baris kedua mengonversi total ke presisi yang cukup untuk 3.5 sebelum pembagian, sehingga operator / melihat sebuah double dan sebuah int, mempromosikan int-nya, dan melakukan pembagian floating-point.
Cukup lakukan cast pada satu operand. Konversi aritmetika biasa akan mengurus yang lain.
Memperbaiki Pembagian Integer
Inilah alasan paling umum untuk menulis cast di C:
Baris persentase itu memberi pelajaran: passed / n adalah 3 / 5, yang bernilai 0 sebagai integer, dan 0 * 100 adalah 0. Mengalikan sebelum membagi (100 * passed / n) memperbaikinya bahkan tanpa cast, karena 300 / 5 habis dibagi, tetapi itu hanya berhasil jika angka-angkanya kebetulan mendukung. Cast adalah perbaikan yang bisa diandalkan.
Dipotong, Bukan Dibulatkan
Melakukan cast nilai floating-point ke integer akan membuang pecahannya. Nilainya dipotong ke arah nol, bukan dibulatkan:
Jika kamu membangun ini di mesinmu sendiri, ingat bahwa math.h butuh -lm saat linking di Linux.
Satu bahaya lagi: mengonversi nilai floating-point yang terlalu besar untuk tipe integernya adalah undefined behavior, bukan wraparound. (int)1e20 bisa menghasilkan apa saja. Cek rentangnya sebelum melakukan cast jika nilainya tidak berada dalam kendalimu.
char dan int
Di C, char adalah integer kecil yang berisi kode karakter. Mengonversi antara keduanya adalah pekerjaan sehari-hari:
digit - '0' adalah idiom standar untuk mengubah karakter digit menjadi nilainya, dan cara ini berhasil karena sepuluh karakter digit dijamin berurutan. Untuk huruf, pilih toupper() dan tolower() dari ctype.h daripada aritmetika + 32, karena selisih itu adalah fakta ASCII, bukan jaminan C.
Jebakan yang berkaitan: fungsi di ctype.h seperti isdigit dan toupper menerima int yang harus berupa EOF atau bisa direpresentasikan sebagai unsigned char. Mengirim char biasa yang bernilai negatif (mungkin terjadi, karena char biasa bisa bersifat signed) adalah perilaku tak terdefinisi. Lakukan cast: isdigit((unsigned char)c).
Jebakan Signed/Unsigned
Langkah 5 dari urutan konversi, unsigned menang saat peringkatnya sama, menghasilkan perbandingan paling mengejutkan di C:
-1 dikonversi menjadi unsigned int, yang menafsirkan ulang pola bit-nya sebagai 4.294.967.295. Nilai itu lebih besar dari 1, jadi perbandingannya bernilai salah.
Konversi yang sama membuat perulangan berjalan selamanya:
/* BUG: i is unsigned, so i >= 0 is always true. When i is 0, i-- wraps. */
for (size_t i = n - 1; i >= 0; i--) { ... }
Dan membuat pengecekan panjang gagal:
/* BUG: strlen returns size_t (unsigned). If the string is shorter
than 5, len - 5 wraps to a huge number and the test passes. */
if (strlen(s) - 5 > 0) { ... }
Tulis ulang menjadi if (strlen(s) > 5) dan pengurangannya tidak pernah terjadi.
Cara bertahannya: simpan jumlah dan indeks dalam satu jenis signedness dari awal sampai akhir, compile dengan -Wsign-compare (sudah termasuk di -Wextra), dan jika memang harus mencampurnya, lakukan cast eksplisit setelah memastikan nilainya tidak mungkin negatif.
Cast pada Pointer
Cast juga mengonversi antartipe pointer, dan di sini risikonya nyata, karena cast mengubah cara memori ditafsirkan, bukan byte-nya itu sendiri.
Di mesin little-endian, kode ini mencetak 01 00 00 00. Memeriksa representasi objek lewat unsigned char * adalah salah satu dari sedikit cast pointer yang secara eksplisit direstui standar.
Sebagian besar cast pointer lainnya tidak direstui. Membaca int lewat float * melanggar aturan strict aliasing dan merupakan undefined behavior walaupun bisa di-compile; pakai memcpy untuk menafsirkan ulang byte.
Ada dua konvensi yang perlu diketahui. void * bisa dikonversi ke dan dari tipe pointer objek apa pun tanpa cast di C, dan itulah alasan kamu tidak perlu melakukan cast pada hasil malloc:
int *arr = malloc(n * sizeof *arr); /* correct C */
int *arr = (int *)malloc(n * sizeof *arr); /* needless; hides a missing header */
Cast itu wajib di C++, dan karena itulah begitu banyak kode memakainya. Di C, cast itu bisa menyembunyikan kesalahan lupa meng-include <stdlib.h>.
Selain itu, printf("%p", ...) mengharapkan void *, jadi argumen pointer di sana memang benar-benar butuh cast: printf("%p", (void *)p).
Saat Cast Adalah Jawaban yang Salah
Cast membungkam compiler. Kadang compiler-nya justru benar.
long big = 5000000000L;
int small = (int)big; /* the cast hides real data loss */
Jika nilainya memang muat, cast itu mendokumentasikan bahwa kamu sudah mengeceknya. Jika mungkin tidak muat, cast itu mengubah sebuah peringatan menjadi jawaban salah yang diam-diam. Sebelum menulisnya, tanyakan apakah perbaikan yang tepat justru mengganti tipe sebuah variabel: double alih-alih int, size_t alih-alih int, long long alih-alih long. Cast adalah alat yang tepat terutama ketika dua tipe yang sama-sama benar harus bertemu untuk satu operasi, seperti pada (double)sum / count.
Pertanyaan yang sering diajukan
Bagaimana cara melakukan cast di C?
Tulis tipe tujuan di dalam tanda kurung sebelum nilainya: (double)x, (int)3.9, (char)65. Cast berlaku untuk ekspresi yang tepat berada sesudahnya, jadi (double)a / b mengonversi a dulu lalu membagi, sedangkan (double)(a / b) membagi sebagai integer lalu mengonversi hasilnya.
Bagaimana cara mengubah int menjadi float di C?
Assignment melakukannya secara implisit: double d = 5; menyimpan 5.0. Di dalam ekspresi, kamu sering butuh cast eksplisit: (double)total / count memaksa pembagian floating-point, bukan pembagian integer.
Apa yang terjadi saat float di-cast menjadi int di C?
Bagian pecahannya dibuang: dipotong ke arah nol, tidak pernah dibulatkan. (int)3.9 bernilai 3 dan (int)-3.9 bernilai -3. Untuk membulatkan, tambahkan 0.5 sebelum cast untuk bilangan positif, atau pakai round(), floor(), atau ceil() dari math.h.
Kenapa membandingkan int signed dan unsigned memberi jawaban yang salah?
Konversi aritmetika biasa di C mengubah nilai signed menjadi unsigned, jadi -1 < 1u bernilai salah: -1 berubah menjadi bilangan positif yang sangat besar. Simpan jumlah dan ukuran dalam satu jenis signedness, atau lakukan cast eksplisit setelah memastikan nilainya tidak mungkin negatif.