it-swarm-id.com

Reinterpret_cast vs para pemeran C-style

Saya mendengar bahwa reinterpret_cast adalah implementasi yang didefinisikan, tetapi saya tidak tahu apa artinya ini. Bisakah Anda memberikan contoh bagaimana itu bisa salah, dan itu salah, apakah lebih baik menggunakan pemeran C-Style?

31
user103214

Para pemeran C-style tidak lebih baik.

Ini hanya mencoba berbagai cast gaya C++ secara berurutan, sampai menemukan yang bekerja. Itu berarti bahwa ketika bertindak seperti reinterpret_cast, ia memiliki masalah yang sama persis dengan reinterpret_cast. Tetapi di samping itu, ia memiliki masalah-masalah ini:

  • Ia dapat melakukan banyak hal berbeda, dan tidak selalu jelas dari membaca kode jenis pemeran yang akan dipanggil (mungkin berperilaku seperti reinterpret_cast, const_cast atau static_cast, dan mereka melakukan hal yang sangat berbeda)
  • Akibatnya, mengubah kode di sekitarnya dapat mengubah perilaku para pemeran
  • Sulit ditemukan ketika membaca atau mencari kode - reinterpret_cast mudah ditemukan, yang bagus, karena gips jelek dan harus diperhatikan ketika digunakan. Sebaliknya, para pemeran gaya-C (seperti dalam (int)42.0) jauh lebih sulit ditemukan secara andal dengan mencari

Untuk menjawab bagian lain dari pertanyaan Anda, ya, reinterpret_cast didefinisikan-implementasi. Ini berarti bahwa ketika Anda menggunakannya untuk mengkonversi dari, katakanlah, int* ke float*, maka Anda tidak memiliki jaminan bahwa pointer yang dihasilkan akan menunjuk ke alamat yang sama. Bagian itu adalah implementasi yang ditentukan. Tetapi jika Anda mengambil float* dan reinterpret_cast yang dihasilkan kembali menjadi int*, maka Anda akan mendapatkan pointer asli. Bagian itu dijamin.

Tapi sekali lagi, ingat bahwa ini benar apakah Anda menggunakan reinterpret_cast atau pemeran C-style:

int i;
int* p0 = &i;

float* p1 = (float*)p0; // implementation-defined result
float* p2 = reinterpret_cast<float*>(p0); // implementation-defined result

int* p3 = (int*)p1; // guaranteed that p3 == p0
int* p4 = (int*)p2; // guaranteed that p4 == p0
int* p5 = reinterpret_cast<int*>(p1); // guaranteed that p5 == p0
int* p6 = reinterpret_cast<int*>(p2); // guaranteed that p6 == p0
31
jalf

Ini adalah implementasi yang didefinisikan dalam arti bahwa standar tidak (hampir) menentukan bagaimana nilai tipe yang berbeda akan terlihat seperti pada tingkat bit, bagaimana ruang alamat harus terstruktur dan sebagainya. Jadi itu benar-benar platform yang sangat spesifik untuk konversi seperti:

double d;
int &i = reinterpret_cast<int&>(d);

Namun seperti yang dikatakan standar

Ini dimaksudkan untuk tidak mengejutkan bagi mereka yang mengetahui struktur pengalamatan Dari mesin yang mendasarinya.

Jadi, jika Anda tahu apa yang Anda lakukan dan bagaimana semuanya terlihat pada level rendah, tidak ada yang salah.

Para pemain C-style agak mirip dalam arti bahwa ia dapat melakukan reinterpret_cast, tetapi juga "mencoba" static_cast terlebih dahulu dan itu dapat membuang kualifikasi cv (sementara static_cast dan reinterpret_cast tidak bisa) dan melakukan konversi tanpa menghiraukan kontrol akses (lihat 5.4/4 dalam standar C++ 11). Misalnya.:

#include <iostream>

using namespace std;

class A { int x; };
class B { int y; };

class C : A, B { int z; };

int main()
{
  C c;

  // just type pun the pointer to c, pointer value will remain the same
  // only it's type is different.
  B *b1 = reinterpret_cast<B *>(&c);

  // perform the conversion with a semantic of static_cast<B*>(&c), disregarding
  // that B is an unaccessible base of C, resulting pointer will point
  // to the B sub-object in c.
  B *b2 = (B*)(&c);

  cout << "reinterpret_cast:\t" << b1 << "\n";
  cout << "C-style cast:\t\t" << b2 << "\n";
  cout << "no cast:\t\t" << &c << "\n";
}

dan ini adalah output dari ideone:

 reinterpret_cast: 0xbfd84e78 
 Pemeran gaya C: 0xbfd84e7c 
 tidak ada pemeran: 0xbfd84e78 

perhatikan bahwa nilai yang dihasilkan oleh reinterpret_cast persis sama dengan alamat 'c', sementara cor gaya-C menghasilkan pointer offset yang benar.

16

Ada alasan yang sah untuk menggunakan reinterpret_cast, dan untuk alasan ini standar sebenarnya menentukan apa yang terjadi.

Yang pertama adalah menggunakan jenis pointer buram, baik untuk API perpustakaan atau hanya untuk menyimpan berbagai pointer dalam satu array (jelas bersama dengan tipenya). Anda diizinkan untuk mengkonversi pointer ke integer berukuran sesuai dan kemudian kembali ke pointer dan itu akan menjadi pointer yang sama persis. Sebagai contoh:

T b;
intptr_t a = reinterpret_cast<intptr_t>( &b );
T * c = reinterpret_cast<T*>(a);

Dalam kode ini c dijamin mengarah ke objek b seperti yang Anda harapkan. Konversi kembali ke jenis pointer yang berbeda tentu saja tidak terdefinisi (semacam). 

Konversi serupa diizinkan untuk pointer fungsi dan pointer fungsi anggota, tetapi dalam kasus yang terakhir Anda dapat melakukan cast ke/dari pointer fungsi anggota lain hanya untuk memiliki variabel yang enouhg besar.

Kasus kedua adalah untuk menggunakan tipe tata letak standar. Ini adalah sesuatu yang didukung oleh faktor sebelum C++ 11 dan sekarang telah ditentukan dalam standar. Dalam hal ini standar memperlakukan reinterpret_cast sebagai static_cast untuk membatalkan * pertama dan kemudian static_cast ke tipe desination. Ini banyak digunakan ketika melakukan protokol biner di mana struktur data sering memiliki informasi header yang sama dan memungkinkan Anda untuk mengkonversi tipe yang memiliki tata letak yang sama, tetapi berbeda dalam struktur kelas C++.

Dalam kedua kasus ini Anda harus menggunakan operator reinterpret_cast eksplisit daripada C-Style. Meskipun C-style biasanya akan melakukan hal yang sama, itu memiliki bahaya menjadi sasaran operator konversi yang kelebihan beban.

6

C++ memiliki jenis, dan satu-satunya cara mereka biasanya mengkonversi antara satu sama lain adalah dengan operator konversi yang Anda tulis. Secara umum, itulah yang Anda berdua butuhkan dan harus gunakan untuk menulis program Anda.

Namun, kadang-kadang, Anda ingin menafsirkan kembali bit yang mewakili tipe menjadi sesuatu yang lain. Ini biasanya digunakan untuk operasi tingkat sangat rendah dan bukan sesuatu yang biasanya harus Anda gunakan. Untuk kasus-kasus tersebut, Anda dapat menggunakan reinterpret_cast.

Ini adalah implementasi yang didefinisikan karena standar C++ tidak benar-benar mengatakan banyak tentang bagaimana hal-hal sebenarnya harus diletakkan dalam memori. Itu dikendalikan oleh implementasi spesifik C++ Anda. Karena itu, perilaku reinterpret_cast tergantung pada bagaimana kompiler Anda meletakkan struktur dalam memori dan bagaimana mengimplementasikan reinterpret_cast.

Pemain C-style sangat mirip dengan reinterpret_casts, tetapi mereka memiliki sintaks yang jauh lebih sedikit dan tidak direkomendasikan. Berpikir bahwa casting secara inheren merupakan operasi yang jelek dan membutuhkan sintaksis jelek untuk memberitahu programmer bahwa sesuatu yang meragukan sedang terjadi.

Contoh mudah tentang bagaimana ini bisa salah:

std::string a;
double* b;
b = reinterpret_cast<double*>(&a);
*b = 3.4;

Perilaku program itu tidak terdefinisi - kompiler dapat melakukan apa pun yang disukainya. Kemungkinan besar, Anda akan mendapatkan crash ketika destructor string dipanggil, tetapi siapa tahu! Ini mungkin saja merusak tumpukan Anda dan menyebabkan kerusakan pada fungsi yang tidak terkait.

4
Ayjay

Gips reinterpret_cast dan c-style didefinisikan implementasi dan keduanya melakukan hal yang hampir sama. Perbedaannya adalah:
1. reinterpret_cast tidak dapat menghapus constness. Sebagai contoh : 

const unsigned int d = 5;
int *g=reinterpret_cast< int* >( &d );

akan mengeluarkan kesalahan:

error: reinterpret_cast from type 'const unsigned int*' to type 'int*' casts away qualifiers  

2. Jika Anda menggunakan reinterpret_cast, mudah untuk menemukan tempat di mana Anda melakukannya. Itu tidak mungkin dilakukan dengan gips gaya-c

2
BЈовић

Pemain gaya-C kadang-kadang mengetik-pun objek dengan cara yang tidak ditentukan, seperti (unsigned int)-1, terkadang mengonversi nilai yang sama ke format yang berbeda, seperti (double)42, kadang-kadang bisa melakukan keduanya, seperti bagaimana (void*)0xDEADBEEF menafsirkan ulang bit tetapi (void*)0 dijamin menjadi null pointer konstan, yang tidak selalu memiliki representasi objek yang sama dengan (intptr_t)0, dan sangat jarang memberitahu kompiler untuk melakukan sesuatu seperti shoot_self_in_foot_with((char*)&const_object);.

Itu biasanya semua baik dan bagus, tetapi ketika Anda ingin melemparkan double ke uint64_t, terkadang Anda menginginkan nilainya dan terkadang Anda menginginkan bitnya. Jika Anda tahu C, Anda tahu yang mana yang dilakukan pemeran C-style, tetapi lebih baik dalam beberapa hal memiliki sintaks yang berbeda untuk keduanya.

Bjarne Stroustrup, dalam pedomannya, merekomendasikan reinterpret_cast dalam konteks lain: jika Anda ingin mengetik-pun dengan cara yang bahasa tidak didefinisikan oleh static_cast, ia menyarankan Anda melakukannya dengan sesuatu seperti reinterpret_cast<double&>(uint64) daripada metode lainnya. Itu semua adalah perilaku yang tidak terdefinisi, tetapi itu membuatnya sangat eksplisit apa yang Anda lakukan dan bahwa Anda melakukannya dengan sengaja. Membaca anggota serikat yang berbeda dari yang Anda tulis sebelumnya tidak.

0
Davislor