Apa kegunaan yang tepat dari:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Pemain gaya-C
(type)value
- Pemain bergaya fungsi
type(value)
Bagaimana seseorang memutuskan mana yang akan digunakan dalam kasus tertentu?
Apa kegunaan yang tepat dari:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Bagaimana seseorang memutuskan mana yang akan digunakan dalam kasus tertentu?
Jawaban:
static_cast
adalah pemeran pertama yang harus Anda coba gunakan. Itu melakukan hal-hal seperti konversi implisit antara jenis (seperti int
untuk float
, atau penunjuk ke void*
), dan juga dapat memanggil fungsi konversi eksplisit (atau yang implisit). Dalam banyak kasus, menyatakan secara eksplisit static_cast
tidak perlu, tetapi penting untuk dicatat bahwa T(something)
sintaksinya setara dengan (T)something
dan harus dihindari (lebih lanjut tentang itu nanti). T(something, something_else)
Namun, A aman dan dijamin untuk memanggil konstruktor.
static_cast
juga dapat dilemparkan melalui hierarki warisan. Tidak perlu ketika casting ke atas (menuju kelas dasar), tetapi ketika casting ke bawah itu dapat digunakan selama tidak dibuang melalui virtual
warisan. Itu tidak melakukan pengecekan, bagaimanapun, dan itu adalah perilaku tidak terdefinisi untuk static_cast
menurunkan hierarki ke tipe yang sebenarnya bukan tipe objek.
const_cast
dapat digunakan untuk menghapus atau menambah const
variabel; tidak ada pemain C ++ lainnya yang mampu menghapusnya (bahkan tidak reinterpret_cast
). Penting untuk dicatat bahwa memodifikasi const
nilai sebelumnya hanya tidak terdefinisi jika variabel aslinya const
; jika Anda menggunakannya untuk const
melepas referensi ke sesuatu yang tidak dideklarasikan dengannya const
, itu aman. Ini bisa bermanfaat ketika kelebihan fungsi anggota berdasarkan const
, misalnya. Itu juga dapat digunakan untuk menambah const
objek, seperti untuk memanggil fungsi anggota yang berlebihan.
const_cast
juga bekerja dengan cara yang sama volatile
, meskipun itu kurang umum.
dynamic_cast
secara eksklusif digunakan untuk menangani polimorfisme. Anda bisa melemparkan pointer atau referensi ke tipe polimorfik apa pun ke tipe kelas lain (tipe polimorfik memiliki setidaknya satu fungsi virtual, dideklarasikan atau diwarisi). Anda dapat menggunakannya untuk lebih dari sekedar melakukan casting ke bawah - Anda dapat melakukan gerakan sideways atau bahkan ke atas. The dynamic_cast
akan mencari objek yang diinginkan dan mengembalikannya jika mungkin. Jika tidak bisa, itu akan kembali nullptr
dalam kasus penunjuk, atau melempar std::bad_cast
dalam kasus referensi.
dynamic_cast
memiliki beberapa keterbatasan. Ini tidak berfungsi jika ada beberapa objek dari jenis yang sama dalam hierarki warisan (yang disebut 'berlian yang ditakuti') dan Anda tidak menggunakan virtual
warisan. Itu juga hanya bisa melalui warisan publik - itu akan selalu gagal untuk melakukan perjalanan melalui protected
atau private
warisan. Namun, ini jarang menjadi masalah, karena bentuk-bentuk warisan seperti itu jarang terjadi.
reinterpret_cast
adalah pemain yang paling berbahaya, dan harus digunakan sangat hemat. Itu mengubah satu jenis langsung ke yang lain - seperti melemparkan nilai dari satu pointer ke yang lain, atau menyimpan pointer di dalam int
, atau segala macam hal buruk lainnya. Sebagian besar, satu-satunya jaminan yang Anda dapatkan reinterpret_cast
adalah bahwa jika Anda mengembalikan hasilnya ke jenis aslinya, Anda akan mendapatkan nilai yang sama persis (tetapi tidak jika jenis perantara lebih kecil dari jenis aslinya). Ada sejumlah konversi yang reinterpret_cast
tidak dapat dilakukan juga. Ini digunakan terutama untuk konversi aneh dan manipulasi bit, seperti mengubah aliran data mentah menjadi data aktual, atau menyimpan data dalam bit rendah dari pointer untuk menyelaraskan data.
Pemain gaya-C dan pemain gaya- fungsi adalah masing-masing pemain yang menggunakan (type)object
atau type(object)
, dan secara fungsional setara. Mereka didefinisikan sebagai yang pertama dari berikut yang berhasil:
const_cast
static_cast
(meskipun mengabaikan batasan akses)static_cast
(lihat di atas), lalu const_cast
reinterpret_cast
reinterpret_cast
, kemudian const_cast
Karena itu dapat digunakan sebagai pengganti gips lain dalam beberapa kasus, tetapi bisa sangat berbahaya karena kemampuan untuk berubah menjadi reinterpret_cast
, dan yang terakhir harus dipilih ketika pengecoran eksplisit diperlukan, kecuali Anda yakin static_cast
akan berhasil atau reinterpret_cast
akan gagal . Meski begitu, pertimbangkan opsi yang lebih panjang dan lebih eksplisit.
static_cast
Pemain gaya-C juga mengabaikan kontrol akses saat melakukan , yang berarti mereka memiliki kemampuan untuk melakukan operasi yang tidak bisa dilakukan oleh pemain lain. Ini sebagian besar adalah kludge, dan dalam pikiran saya hanyalah alasan lain untuk menghindari gips C-style.
const
(bahkan tidak reinterpret_cast
)" ... benarkah? Bagaimana dengan reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
seringkali merupakan senjata pilihan ketika berhadapan dengan set data API tipe API yang tidak jelas
Gunakan dynamic_cast
untuk mengkonversi pointer / referensi dalam hierarki warisan.
Gunakan static_cast
untuk konversi jenis biasa.
Gunakan reinterpret_cast
untuk menafsirkan ulang pola bit tingkat rendah. Gunakan dengan sangat hati-hati.
Gunakan const_cast
untuk membuang const/volatile
. Hindari ini kecuali Anda terjebak menggunakan API const-salah.
(Banyak penjelasan teoretis dan konseptual telah diberikan di atas)
Di bawah ini adalah beberapa contoh praktis ketika saya menggunakan static_cast , dynamic_cast , const_cast , reinterpret_cast .
(Juga rujuk ini untuk memahami penjelasannya: http://www.cplusplus.com/doc/tutorial/typecasting/ )
static_cast:
OnEventData(void* pData)
{
......
// pData is a void* pData,
// EventData is a structure e.g.
// typedef struct _EventData {
// std::string id;
// std:: string remote_id;
// } EventData;
// On Some Situation a void pointer *pData
// has been static_casted as
// EventData* pointer
EventData *evtdata = static_cast<EventData*>(pData);
.....
}
dynamic_cast:
void DebugLog::OnMessage(Message *msg)
{
static DebugMsgData *debug;
static XYZMsgData *xyz;
if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
// debug message
}
else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
// xyz message
}
else/* if( ... )*/{
// ...
}
}
const_cast:
// *Passwd declared as a const
const unsigned char *Passwd
// on some situation it require to remove its constness
const_cast<unsigned char*>(Passwd)
reinterpret_cast:
typedef unsigned short uint16;
// Read Bytes returns that 2 bytes got read.
bool ByteBuffer::ReadUInt16(uint16& val) {
return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
static_cast<char*>(&val)
?
static_cast
hanya berfungsi di antara jenis dengan konversi yang ditentukan, hubungan yang terlihat menurut pewarisan, atau ke / dari void *
. Untuk yang lainnya, ada gips lainnya. reinterpret cast
untuk char *
jenis apa pun diizinkan untuk memungkinkan membaca representasi objek apa pun - dan satu-satunya kasus di mana kata kunci tersebut berguna, bukan generator yang merajalela dari implementasi- / perilaku tidak terdefinisi. Tetapi ini tidak dianggap sebagai konversi 'normal', jadi tidak diperbolehkan oleh (biasanya) sangat konservatif static_cast
.
Mungkin membantu jika Anda tahu sedikit tentang internal ...
static_cast
static_cast
untuk mereka.A
ke B
, konstruktor static_cast
panggilan B
lewat A
sebagai param. Atau, A
dapat memiliki operator konversi (yaitu A::operator B()
). Jika B
tidak memiliki konstruktor seperti itu, atau A
tidak memiliki operator konversi, maka Anda mendapatkan kesalahan waktu kompilasi.A*
untuk B*
selalu berhasil jika A dan B berada dalam hierarki warisan (atau batal) jika tidak Anda mendapatkan kesalahan kompilasi.A&
untuk B&
.dynamic_cast
(Base*)
untuk (Derived*)
dapat gagal jika pointer tidak benar-benar tipe berasal.A*
ke B*
, jika casting tidak valid maka dynamic_cast akan mengembalikan nullptr.A&
ke B&
jika pemain tidak valid maka dynamic_cast akan membuang pengecualian bad_cast.const_cast
set<T>
yang hanya mengembalikan elemen-elemennya sebagai const untuk memastikan Anda tidak mengubah kuncinya. Namun jika maksud Anda adalah untuk memodifikasi anggota non-kunci objek maka itu harus ok. Anda dapat menggunakan const_cast untuk menghapus constness.T& SomeClass::foo()
juga const T& SomeClass::foo() const
. Untuk menghindari duplikasi kode, Anda dapat menerapkan const_cast untuk mengembalikan nilai satu fungsi dari yang lain.reinterpret_cast
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.
Anda mendapatkan UB yang dapat menghasilkan segfault saat runtime jika Anda beruntung. 2. Cor dinamis dapat juga digunakan dalam cross casting. 3. Pemain cast dapat menghasilkan UB dalam beberapa kasus. Menggunakan mutable
mungkin merupakan pilihan yang lebih baik untuk menerapkan keteguhan logis.
mutable
, cross casting dll.
Apakah ini menjawab pertanyaan Anda?
Saya tidak pernah menggunakan reinterpret_cast
, dan bertanya-tanya apakah berlari ke case yang membutuhkannya bukan bau desain yang buruk. Pada basis kode yang saya kerjakan banyak dynamic_cast
digunakan. Bedanya dengan static_cast
adalah bahwa dynamic_cast
tidak pengecekan runtime yang mungkin (lebih aman) atau mungkin tidak (overhead) menjadi apa yang Anda inginkan (lihat MSDN ).
reinterpret_cast
untuk mengekstrak potongan data dari array. Sebagai contoh jika saya memiliki char*
buffer besar yang penuh dengan data biner yang saya butuhkan untuk bergerak dan mendapatkan primitif individu dari berbagai jenis. Sesuatu seperti ini:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
, tidak ada banyak kegunaan untuk itu.
reinterpret_cast
digunakan untuk satu alasan. Saya telah melihat data objek mentah disimpan ke tipe data "gumpalan" dalam database, kemudian ketika data tersebut diambil dari database, reinterpret_cast
digunakan untuk mengubah data mentah ini menjadi objek.
Selain jawaban lain sejauh ini, berikut adalah contoh yang tidak jelas di mana static_cast
tidak cukup sehingga reinterpret_cast
diperlukan. Misalkan ada fungsi yang dalam parameter output mengembalikan pointer ke objek dari kelas yang berbeda (yang tidak berbagi kelas basis umum). Contoh nyata dari fungsi tersebut adalah CoCreateInstance()
(lihat parameter terakhir, yang sebenarnya void**
). Misalkan Anda meminta kelas objek tertentu dari fungsi ini, jadi Anda tahu sebelumnya jenis untuk pointer (yang sering Anda lakukan untuk objek COM). Dalam hal ini Anda tidak dapat memasukkan pointer ke pointer Anda void**
dengan static_cast
: you need reinterpret_cast<void**>(&yourPointer)
.
Dalam kode:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
//static_cast<void**>(&pNetFwPolicy2) would give a compile error
reinterpret_cast<void**>(&pNetFwPolicy2) );
Namun, static_cast
berfungsi untuk pointer sederhana (bukan pointer ke pointer), sehingga kode di atas dapat ditulis ulang untuk menghindari reinterpret_cast
(dengan harga variabel tambahan) dengan cara berikut:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
&static_cast<void*>(pNetFwPolicy2)
bukan static_cast<void**>(&pNetFwPolicy2)
?
Sementara jawaban lain menggambarkan dengan baik semua perbedaan antara cast C ++, saya ingin menambahkan catatan singkat mengapa Anda tidak harus menggunakan cast C-style (Type) var
dan Type(var)
.
Untuk pemain C ++ pemula C-style terlihat seperti menjadi operasi superset lebih dari C ++ gips (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) dan seseorang dapat lebih memilih mereka daripada pemain C ++ . Sebenarnya pemeran C-style adalah superset dan lebih pendek untuk menulis.
Masalah utama para pemain C-style adalah mereka menyembunyikan niat sebenarnya dari para pemain. Gips C-style dapat melakukan hampir semua jenis gips dari gips yang biasanya aman dilakukan oleh static_cast <> () dan dynamic_cast <> () ke gips yang berpotensi berbahaya seperti const_cast <> (), di mana pengubah konst dapat dihapus sehingga variabel konst dapat dimodifikasi dan reinterpret_cast <> () yang bahkan dapat menafsirkan kembali nilai integer ke pointer.
Ini contohnya.
int a=rand(); // Random number.
int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.
int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.
int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.
*pa4=5; // Program crashes.
Alasan utama mengapa para pemain C ++ ditambahkan ke dalam bahasa ini adalah untuk memungkinkan pengembang untuk mengklarifikasi niatnya - mengapa ia akan melakukan pemeran itu. Dengan menggunakan gips C-style yang benar-benar valid di C ++ Anda membuat kode Anda kurang mudah dibaca dan lebih rentan kesalahan terutama untuk pengembang lain yang tidak membuat kode Anda. Jadi untuk membuat kode Anda lebih mudah dibaca dan eksplisit, Anda harus selalu lebih suka cast C ++ daripada cast gaya-C.
Berikut adalah kutipan pendek dari buku Bjarne Stroustrup (penulis C ++) The C ++ Programming Language edisi ke 4 - halaman 302.
Pemain gaya C ini jauh lebih berbahaya daripada operator konversi yang disebutkan karena notasi lebih sulit dikenali dalam program besar dan jenis konversi yang dimaksudkan oleh programmer tidak eksplisit.
Untuk memahami, mari pertimbangkan potongan kode di bawah ini:
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
Hanya baris (4) yang dikompilasi tanpa kesalahan. Hanya reinterpret_cast yang dapat digunakan untuk mengonversi pointer ke objek ke pointer ke jenis objek apa pun yang tidak terkait.
Satu hal yang perlu diperhatikan adalah: dynamic_cast akan gagal pada saat run-time, tetapi pada kebanyakan kompiler, ia juga akan gagal untuk dikompilasi karena tidak ada fungsi virtual dalam struct dari pointer yang sedang di-cast, artinya dynamic_cast hanya akan bekerja dengan pointer kelas polymorphic saja .
Kapan menggunakan C ++ cast :
static_cast
vs dynamic_cast
vs reinterpret_cast
internal melihat pada downcast / upcast
Dalam jawaban ini, saya ingin membandingkan tiga mekanisme ini pada contoh konkret / downcast konkret dan menganalisis apa yang terjadi pada pointer / memori / perakitan yang mendasari untuk memberikan pemahaman konkret tentang bagaimana mereka membandingkan.
Saya percaya bahwa ini akan memberikan intuisi yang baik tentang bagaimana para pemain berbeda:
static_cast
: apakah satu alamat offset pada saat runtime (dampak runtime rendah) dan tidak ada keamanan yang memeriksa bahwa downcast benar.
dyanamic_cast
: apakah alamat yang sama diimbangi pada saat runtime suka static_cast
, tetapi juga dan pemeriksaan keamanan yang mahal bahwa downcast benar menggunakan RTTI.
Pemeriksaan keamanan ini memungkinkan Anda untuk menanyakan apakah pointer kelas dasar adalah tipe yang diberikan saat runtime dengan memeriksa pengembalian nullptr
yang mengindikasikan downcast yang tidak valid.
Oleh karena itu, jika kode Anda tidak dapat memeriksa itu nullptr
dan mengambil tindakan non-abort yang valid, Anda harus menggunakan static_cast
alih-alih pemeran dinamis.
Jika abort adalah satu-satunya tindakan yang dapat dilakukan oleh kode Anda, mungkin Anda hanya ingin mengaktifkan dynamic_cast
in debug builds ( -NDEBUG
), dan gunakan static_cast
sebaliknya, mis. Seperti yang dilakukan di sini , untuk tidak memperlambat lari cepat Anda.
reinterpret_cast
: tidak melakukan apa pun saat runtime, bahkan alamat tidak diimbangi. Pointer harus menunjuk tepat ke tipe yang benar, bahkan kelas dasar tidak berfungsi. Anda biasanya tidak menginginkan ini kecuali stream byte mentah terlibat.
Perhatikan contoh kode berikut:
main.cpp
#include <iostream>
struct B1 {
B1(int int_in_b1) : int_in_b1(int_in_b1) {}
virtual ~B1() {}
void f0() {}
virtual int f1() { return 1; }
int int_in_b1;
};
struct B2 {
B2(int int_in_b2) : int_in_b2(int_in_b2) {}
virtual ~B2() {}
virtual int f2() { return 2; }
int int_in_b2;
};
struct D : public B1, public B2 {
D(int int_in_b1, int int_in_b2, int int_in_d)
: B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
void d() {}
int f2() { return 3; }
int int_in_d;
};
int main() {
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1, 2, 3};
// The memory layout must support the virtual method call use case.
b2s[0] = &b2;
// An upcast is an implicit static_cast<>().
b2s[1] = &d;
std::cout << "&d " << &d << std::endl;
std::cout << "b2s[0] " << b2s[0] << std::endl;
std::cout << "b2s[1] " << b2s[1] << std::endl;
std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;
// Now for some downcasts.
// Cannot be done implicitly
// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
// dp = (b2s[0]);
// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp = static_cast<D*>(b2s[0]);
std::cout << "static_cast<D*>(b2s[0]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = static_cast<D*>(b2s[1]);
std::cout << "static_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Segfault because dp is nullptr.
dp = dynamic_cast<D*>(b2s[0]);
std::cout << "dynamic_cast<D*>(b2s[0]) " << dp << std::endl;
//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = dynamic_cast<D*>(b2s[1]);
std::cout << "dynamic_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Undefined behaviour to an unrelated memory address because this
// did not calculate the offset to get from B2* to D*.
dp = reinterpret_cast<D*>(b2s[1]);
std::cout << "reinterpret_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}
Kompilasi, jalankan, dan bongkar dengan:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
mana setarch
yang digunakan untuk ASLR menonaktifkan untuk membuatnya lebih mudah untuk membandingkan berjalan.
Output yang mungkin:
&d 0x7fffffffc930
b2s[0] 0x7fffffffc920
b2s[1] 0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0]) 0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d 1
static_cast<D*>(b2s[1]) 0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d 3
dynamic_cast<D*>(b2s[0]) 0
dynamic_cast<D*>(b2s[1]) 0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767
Sekarang, seperti yang disebutkan di: https://en.wikipedia.org/wiki/Virtual_method_table untuk mendukung panggilan metode virtual secara efisien, struktur data memori D
harus terlihat seperti:
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
D:
+0: pointer to virtual method table of D (for B1)
+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)
+12: value of int_in_b2
+16: value of int_in_d
Fakta kuncinya adalah bahwa struktur data memori D
berisi di dalamnya struktur memori yang kompatibel dengan struktur internal B1
dan B2
internal.
Karena itu kami mencapai kesimpulan kritis:
upcast atau downcast hanya perlu menggeser nilai pointer dengan nilai yang diketahui pada waktu kompilasi
Dengan cara ini, ketika D
diteruskan ke array tipe dasar, pemeran tipe sebenarnya menghitung offset itu dan menunjukkan sesuatu yang tampak persis seperti yang valid B2
dalam memori:
b2s[1] = &d;
kecuali bahwa ini memiliki vtable untuk D
bukan B2
, dan karena itu semua panggilan virtual bekerja secara transparan.
Sekarang, kita akhirnya bisa kembali mengetik pengecoran dan analisis contoh konkret kami.
Dari output stdout kita melihat:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Oleh karena itu, implisit yang static_cast
dilakukan di sana benar menghitung offset dari D
struktur data lengkap pada 0x7fffffffc930 ke yang B2
sejenisnya yaitu pada 0x7fffffffc940. Kami juga menyimpulkan bahwa apa yang terletak antara 0x7fffffffc930 dan 0x7fffffffc940 kemungkinan adalah B1
data dan vtable.
Kemudian, pada bagian yang tertekan, sekarang mudah untuk memahami bagaimana yang tidak valid gagal dan mengapa:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: kompiler baru saja naik 0x10 pada waktu kompilasi byte untuk mencoba dan pergi dari B2
ke yang berisiD
Tetapi karena b2s[0]
bukan D
, itu sekarang menunjuk ke wilayah memori yang tidak ditentukan.
Pembongkarannya adalah:
49 dp = static_cast<D*>(b2s[0]);
0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax
0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433>
0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax
0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438>
0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)
jadi kita melihat bahwa GCC tidak:
D
yang tidak adadynamic_cast<D*>(b2s[0]) 0
: C ++ sebenarnya menemukan bahwa para pemain tidak valid dan dikembalikan nullptr
!
Tidak ada cara ini dapat dilakukan pada waktu kompilasi, dan kami akan mengkonfirmasi itu dari pembongkaran:
59 dp = dynamic_cast<D*>(b2s[0]);
0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax
0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744>
0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx
0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D>
0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2>
0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi
0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt>
0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749>
0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)
Pertama ada cek NULL, dan mengembalikan NULL jika einput adalah NULL.
Kalau tidak, itu mengatur beberapa argumen dalam RDX, RSI dan RDI dan panggilan __dynamic_cast
.
Saya tidak memiliki kesabaran untuk menganalisis ini lebih jauh sekarang, tetapi seperti yang orang lain katakan, satu-satunya cara agar ini bekerja adalah __dynamic_cast
dengan mengakses beberapa struktur data memori RTTI tambahan yang mewakili hirarki kelas.
Oleh karena itu harus dimulai dari B2
entri untuk tabel itu, kemudian berjalan hirarki kelas ini sampai menemukan bahwa vtable untuk D
typecast dari b2s[0]
.
Inilah sebabnya mengapa menafsirkan ulang pemain berpotensi mahal! Berikut adalah contoh di mana tambalan satu liner yang mengubah a dynamic_cast
menjadistatic_cast
dalam proyek kompleks mengurangi runtime sebesar 33%! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
yang ini hanya mempercayai kita secara membabi buta: kita mengatakan ada D
alamat b2s[1]
, dan kompiler tidak melakukan perhitungan offset.
Tapi ini salah, karena D sebenarnya di 0x7fffffffc930, apa yang ada di 0x7fffffffc940 adalah struktur seperti B2 di dalam D! Jadi sampah diakses.
Kami dapat mengkonfirmasi ini dari -O0
majelis menghebohkan yang hanya memindahkan nilai sekitar:
70 dp = reinterpret_cast<D*>(b2s[1]);
0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax
0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)
Pertanyaan-pertanyaan Terkait:
Diuji pada Ubuntu 18.04 amd64, GCC 7.4.0.