Bagikan melalui


Konversi jenis dan keamanan jenis

Dokumen ini mengidentifikasi masalah konversi jenis umum dan menjelaskan bagaimana Anda dapat menghindarinya dalam kode C++ Anda.

Saat Anda menulis program C++, penting untuk memastikan bahwa program tersebut aman. Ini berarti bahwa setiap variabel, argumen fungsi, dan nilai pengembalian fungsi menyimpan jenis data yang dapat diterima, dan bahwa operasi yang melibatkan nilai dari berbagai jenis "masuk akal" dan tidak menyebabkan kehilangan data, interpretasi pola bit yang salah, atau kerusakan memori. Program yang tidak pernah secara eksplisit atau implisit mengonversi nilai dari satu jenis ke jenis lainnya aman berdasarkan definisi. Namun, konversi jenis, bahkan konversi yang tidak aman, terkadang diperlukan. Misalnya, Anda mungkin harus menyimpan hasil operasi floating point dalam variabel jenis int, atau Anda mungkin harus meneruskan nilai ke unsigned int fungsi yang mengambil signed int. Kedua contoh mengilustrasikan konversi yang tidak aman karena dapat menyebabkan kehilangan data atau interpretasi ulang nilai.

Saat pengkompilasi mendeteksi konversi yang tidak aman, pengkompilasi akan mengeluarkan kesalahan atau peringatan. Kesalahan menghentikan kompilasi; peringatan memungkinkan kompilasi berlanjut tetapi menunjukkan kemungkinan kesalahan dalam kode. Namun, bahkan jika program Anda dikompilasi tanpa peringatan, program tersebut masih mungkin berisi kode yang mengarah ke konversi jenis implisit yang menghasilkan hasil yang salah. Kesalahan jenis juga dapat diperkenalkan oleh konversi eksplisit, atau transmisi, dalam kode.

Konversi jenis implisit

Saat ekspresi berisi operan dari berbagai jenis bawaan, dan tidak ada cast eksplisit yang ada, pengkompilasi menggunakan konversi standar bawaan untuk mengonversi salah satu operand sehingga jenisnya cocok. Pengkompilasi mencoba konversi dalam urutan yang terdefinisi dengan baik sampai berhasil. Jika konversi yang dipilih adalah promosi, pengkompilasi tidak mengeluarkan peringatan. Jika konversi adalah penyempitan, pengkompilasi mengeluarkan peringatan tentang kemungkinan kehilangan data. Apakah kehilangan data aktual terjadi tergantung pada nilai aktual yang terlibat, tetapi kami sarankan Anda memperlakukan peringatan ini sebagai kesalahan. Jika jenis yang ditentukan pengguna terlibat, pengkompilasi mencoba menggunakan konversi yang telah Anda tentukan dalam definisi kelas. Jika tidak dapat menemukan konversi yang dapat diterima, pengkompilasi mengeluarkan kesalahan dan tidak mengkompilasi program. Untuk informasi selengkapnya tentang aturan yang mengatur konversi standar, lihat Konversi Standar. Untuk informasi selengkapnya tentang konversi yang ditentukan pengguna, lihat KonversiUser-Defined (C++/CLI).

Melebarkan konversi (promosi)

Dalam konversi yang melebar, nilai dalam variabel yang lebih kecil ditetapkan ke variabel yang lebih besar tanpa kehilangan data. Karena konversi yang melebar selalu aman, pengkompilasi melakukannya secara diam-diam dan tidak mengeluarkan peringatan. Konversi berikut adalah konversi yang melebar.

Dari Untuk
Jenis apa pun signed atau unsigned integral kecuali long long atau __int64 double
bool atau char Jenis bawaan lainnya
short atau wchar_t int, , longlong long
int, long long long
float double

Konversi yang mempersempit (koersi)

Kompilator melakukan konversi yang menyempit secara implisit, tetapi memperingatkan Anda tentang potensi kehilangan data. Perhatikan peringatan ini dengan sangat serius. Jika Anda yakin bahwa tidak ada kehilangan data yang akan terjadi karena nilai dalam variabel yang lebih besar akan selalu pas dalam variabel yang lebih kecil, maka tambahkan cast eksplisit sehingga pengkompilasi tidak akan lagi mengeluarkan peringatan. Jika Anda tidak yakin bahwa konversi aman, tambahkan ke kode Anda beberapa jenis pemeriksaan runtime untuk menangani kemungkinan kehilangan data sehingga tidak menyebabkan program Anda menghasilkan hasil yang salah.

Konversi apa pun dari jenis titik mengambang ke jenis integral adalah konversi yang mempersempit karena bagian pecahan dari nilai titik mengambang dibuang dan hilang.

Contoh kode berikut menunjukkan beberapa konversi yang menyempitkan implisit, dan peringatan bahwa masalah pengkompilasi untuk mereka.

int i = INT_MAX + 1; //warning C4307:'+':integral constant overflow
wchar_t wch = 'A'; //OK
char c = wch; // warning C4244:'initializing':conversion from 'wchar_t'
              // to 'char', possible loss of data
unsigned char c2 = 0xfffe; //warning C4305:'initializing':truncation from
                           // 'int' to 'unsigned char'
int j = 1.9f; // warning C4244:'initializing':conversion from 'float' to
              // 'int', possible loss of data
int k = 7.7; // warning C4244:'initializing':conversion from 'double' to
             // 'int', possible loss of data

Ditandatangani - konversi yang tidak ditandatangani

Jenis integral yang ditandatangani dan mitranya yang tidak ditandatangani selalu berukuran sama, tetapi berbeda dalam bagaimana pola bit ditafsirkan untuk transformasi nilai. Contoh kode berikut menunjukkan apa yang terjadi ketika pola bit yang sama ditafsirkan sebagai nilai yang ditandatangani dan sebagai nilai yang tidak ditandatangani. Pola bit disimpan dalam keduanya num dan num2 tidak pernah berubah dari apa yang ditunjukkan dalam ilustrasi sebelumnya.

using namespace std;
unsigned short num = numeric_limits<unsigned short>::max(); // #include <limits>
short num2 = num;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: "unsigned val = 65535 signed val = -1"

// Go the other way.
num2 = -1;
num = num2;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: "unsigned val = 65535 signed val = -1"

Perhatikan bahwa nilai diinterpretasikan ulang di kedua arah. Jika program Anda menghasilkan hasil ganjil di mana tanda nilai tampak terbalik dari apa yang Anda harapkan, cari konversi implisit antara jenis integral yang ditandatangani dan tidak ditandatangani. Dalam contoh berikut, hasil ekspresi ( 0 - 1) secara implisit dikonversi dari int ke unsigned int ketika disimpan di num. Hal ini menyebabkan pola bit diinterpretasikan kembali.

unsigned int u3 = 0 - 1;
cout << u3 << endl; // prints 4294967295

Pengkompilasi tidak memperingatkan tentang konversi implisit antara jenis integral yang ditandatangani dan tidak ditandatangani. Jadi, kami sarankan Anda menghindari konversi yang ditandatangani ke yang tidak ditandatangani sama sekali. Jika Anda tidak dapat menghindarinya, tambahkan pemeriksaan runtime untuk mendeteksi apakah nilai yang dikonversi lebih besar dari atau sama dengan nol dan kurang dari atau sama dengan nilai maksimum dari jenis yang ditandatangani. Nilai dalam rentang ini akan ditransfer dari yang ditandatangani ke tidak ditandatangani atau dari tidak ditandatangani ke ditandatangani tanpa diinterpretasikan ulang.

Konversi pointer

Dalam banyak ekspresi, array gaya C secara implisit dikonversi ke penunjuk ke elemen pertama dalam array, dan konversi konstan dapat terjadi secara diam-diam. Meskipun nyaman, ini juga berpotensi rawan kesalahan. Misalnya, contoh kode yang dirancang dengan buruk berikut tampaknya tidak masuk akal, namun akan mengkompilasi dan menghasilkan hasil 'p'. Pertama, literal konstanta string "Bantuan" dikonversi ke char* yang menunjuk ke elemen pertama array; penunjuk tersebut kemudian ditambahkan oleh tiga elemen sehingga sekarang menunjuk ke elemen terakhir 'p'.

char* s = "Help" + 3;

Konversi eksplisit (cast)

Dengan menggunakan operasi transmisi, Anda dapat menginstruksikan pengkompilasi untuk mengonversi nilai satu jenis ke jenis lain. Pengkompilasi akan menimbulkan kesalahan dalam beberapa kasus jika kedua jenis benar-benar tidak terkait, tetapi dalam kasus lain itu tidak akan menimbulkan kesalahan bahkan jika operasi tidak aman jenis. Gunakan transmisi dengan hemat karena konversi apa pun dari satu jenis ke jenis lainnya adalah sumber potensial kesalahan program. Namun, cast kadang-kadang diperlukan, dan tidak semua cast sama-sama berbahaya. Salah satu penggunaan pemeran yang efektif adalah ketika kode Anda melakukan konversi yang mempersempit dan Anda tahu bahwa konversi tidak menyebabkan program Anda menghasilkan hasil yang salah. Akibatnya, ini memberi tahu pengkompilasi bahwa Anda tahu apa yang Anda lakukan dan untuk berhenti mengganggu Anda dengan peringatan tentang hal itu. Penggunaan lain adalah melemparkan dari kelas pointer-to-turunan ke kelas pointer-to-base. Penggunaan lain adalah membuang kesadaran variabel untuk meneruskannya ke fungsi yang memerlukan argumen non-const. Sebagian besar operasi pemeran ini melibatkan beberapa risiko.

Dalam pemrograman gaya C, operator cast gaya C yang sama digunakan untuk semua jenis cast.

(int) x; // old-style cast, old-style syntax
int(x); // old-style cast, functional syntax

Operator cast gaya C identik dengan operator panggilan () dan karenanya tidak mencolok dalam kode dan mudah diabaikan. Keduanya buruk karena sulit dikenali sekilas atau dicari, dan mereka cukup berbeda untuk memanggil kombinasi static, , constdan reinterpret_cast. Mencari tahu apa yang sebenarnya dilakukan pemeran gaya lama bisa sulit dan rawan kesalahan. Untuk semua alasan ini, ketika cast diperlukan, kami sarankan Anda menggunakan salah satu operator cast C++ berikut, yang dalam beberapa kasus secara signifikan lebih aman untuk jenis, dan yang mengekspresikan jauh lebih eksplisit niat pemrograman:

  • static_cast, untuk cast yang diperiksa pada waktu kompilasi saja. static_cast mengembalikan kesalahan jika pengkompilasi mendeteksi bahwa Anda mencoba mentransmisikan di antara jenis yang benar-benar tidak kompatibel. Anda juga dapat menggunakannya untuk mentransmisikan antara pointer-to-base dan pointer-to-turunan, tetapi pengkompilasi tidak selalu dapat mengetahui apakah konversi tersebut akan aman pada runtime.

    double d = 1.58947;
    int i = d;  // warning C4244 possible loss of data
    int j = static_cast<int>(d);       // No warning.
    string s = static_cast<string>(d); // Error C2440:cannot convert from
                                       // double to std::string
    
    // No error but not necessarily safe.
    Base* b = new Base();
    Derived* d2 = static_cast<Derived*>(b);
    

    Untuk informasi selengkapnya, lihat static_cast .

  • dynamic_cast, untuk cast yang aman dan diperiksa runtime dari pointer-to-base ke pointer-to-derived. A dynamic_cast lebih aman daripada static_cast untuk downcast, tetapi pemeriksaan runtime menimbulkan beberapa overhead.

    Base* b = new Base();
    
    // Run-time check to determine whether b is actually a Derived*
    Derived* d3 = dynamic_cast<Derived*>(b);
    
    // If b was originally a Derived*, then d3 is a valid pointer.
    if(d3)
    {
       // Safe to call Derived method.
       cout << d3->DoSomethingMore() << endl;
    }
    else
    {
       // Run-time check failed.
       cout << "d3 is null" << endl;
    }
    
    //Output: d3 is null;
    

    Untuk informasi selengkapnya, lihat dynamic_cast .

  • const_cast, untuk mengusir const-ness variabel, atau mengonversi variabel nonconst menjadi const. Mengusir const-ness dengan menggunakan operator ini sama rawannya kesalahan seperti menggunakan cast gaya C, kecuali bahwa dengan const_cast Anda cenderung tidak melakukan transmisi secara tidak sengaja. Terkadang Anda harus membuang const-ness variabel, misalnya, untuk meneruskan const variabel ke fungsi yang mengambil parameter non-const . Contoh berikut menunjukkan cara melakukannya.

    void Func(double& d) { ... }
    void ConstCast()
    {
       const double pi = 3.14;
       Func(const_cast<double&>(pi)); //No error.
    }
    

    Untuk informasi selengkapnya, lihat const_cast .

  • reinterpret_cast, untuk transmisi antara jenis yang tidak terkait seperti jenis penunjuk dan int.

    Catatan

    Operator transmisi ini tidak digunakan sesering yang lain, dan tidak dijamin portabel untuk pengkompilasi lain.

    Contoh berikut menggambarkan perbedaannya reinterpret_cast dengan static_cast.

    const char* str = "hello";
    int i = static_cast<int>(str);//error C2440: 'static_cast' : cannot
                                  // convert from 'const char *' to 'int'
    int j = (int)str; // C-style cast. Did the programmer really intend
                      // to do this?
    int k = reinterpret_cast<int>(str);// Programming intent is clear.
                                       // However, it is not 64-bit safe.
    

    Untuk informasi selengkapnya, lihat reinterpret_cast Operator.

Lihat juga

Sistem jenis C++
Selamat datang kembali ke C++
Referensi Bahasa C++
Pustaka Standar C++